- {["flex-[4]", "flex-1", "flex-1", "flex-1"].map((flexClass, i) => (
+ {/* Steps skeleton - 3 cards */}
+
+ {["flex-[4]", "flex-1", "flex-1"].map((flexClass, i) => (
))}
diff --git a/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx b/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx
index e8916d894..c9a9a3377 100644
--- a/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx
+++ b/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx
@@ -7,7 +7,7 @@ import { useAxiosInstance } from "@/services/useAxiosInstance";
const REFETCH_INTERVAL = 5000;
const DISMISSED_STORAGE_KEY = "autumn_products_onboarding_dismissed";
-export type OnboardingStepId = "plans" | "customer" | "payments" | "usage";
+export type OnboardingStepId = "plans" | "customer" | "usage";
interface OnboardingStepStatus {
complete: boolean;
@@ -94,26 +94,6 @@ export const useOnboardingProgress = (): OnboardingProgress => {
},
});
- // Payments query (checking for Stripe ID)
- const { data: paymentsData, isLoading: paymentsLoading } = useQuery<{
- fullCustomers: FullCustomer[];
- }>({
- queryKey: ["onboarding-payments"],
- queryFn: async () => {
- const { data } = await axiosInstance.post(
- "/customers/all/full_customers",
- { page_size: 50 },
- );
- return data;
- },
- refetchInterval: (query) => {
- const hasStripeCustomer = query.state.data?.fullCustomers?.some(
- (c) => c.processor?.id,
- );
- return hasStripeCustomer ? false : REFETCH_INTERVAL;
- },
- });
-
// Events query
const { data: eventsData, isLoading: eventsLoading } = useQuery<{
rawEvents: { data: unknown[] };
@@ -145,29 +125,24 @@ export const useOnboardingProgress = (): OnboardingProgress => {
return hasPrice && hasFeature;
}) ?? false,
customer: (customersData?.fullCustomers?.length ?? 0) > 0,
- payments:
- paymentsData?.fullCustomers?.some((c) => c.processor?.id) ?? false,
usage: (eventsData?.rawEvents?.data?.length ?? 0) > 0,
}),
- [productsData, customersData, paymentsData, eventsData],
+ [productsData, customersData, eventsData],
);
const currentStep = useMemo((): OnboardingStepId => {
if (!completedSteps.plans) return "plans";
if (!completedSteps.customer) return "customer";
- if (!completedSteps.payments) return "payments";
if (!completedSteps.usage) return "usage";
return "plans";
}, [completedSteps]);
- const isLoading =
- productsLoading || customersLoading || paymentsLoading || eventsLoading;
+ const isLoading = productsLoading || customersLoading || eventsLoading;
return {
steps: {
plans: { complete: completedSteps.plans },
customer: { complete: completedSteps.customer },
- payments: { complete: completedSteps.payments },
usage: { complete: completedSteps.usage },
},
currentStep,
diff --git a/vite/src/views/onboarding4/hooks/usePricingAgentChat.ts b/vite/src/views/onboarding4/hooks/usePricingAgentChat.ts
index f388a6e99..64d7bd66d 100644
--- a/vite/src/views/onboarding4/hooks/usePricingAgentChat.ts
+++ b/vite/src/views/onboarding4/hooks/usePricingAgentChat.ts
@@ -142,51 +142,53 @@ export function usePricingAgentChat(options?: UsePricingAgentChatOptions) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- const { messages, sendMessage, status, addToolOutput, setMessages } = useChat({
- transport: new DefaultChatTransport({
- api: `${import.meta.env.VITE_BACKEND_URL}/pricing-agent/chat`,
- credentials: "include",
- headers: {
- "x-client-type": "dashboard",
+ const { messages, sendMessage, status, addToolOutput, setMessages } = useChat(
+ {
+ transport: new DefaultChatTransport({
+ api: `${import.meta.env.VITE_BACKEND_URL}/pricing-agent/chat`,
+ credentials: "include",
+ headers: {
+ "x-client-type": "dashboard",
+ },
+ body: {
+ sessionId: chatSessionIdRef.current,
+ initialConfig: options?.initialConfig ?? null,
+ },
+ }),
+
+ // Auto-submit when all tool results are available (for multi-step if needed)
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
+
+ // Handle client-side tool execution
+ onToolCall: async ({ toolCall }) => {
+ // Check for dynamic tools first
+ if (toolCall.dynamic) {
+ return;
+ }
+
+ if (toolCall.toolName === "build_pricing") {
+ const config = toolCall.input as AgentPricingConfig;
+
+ // Update the pricing preview
+ setPricingConfig(config);
+
+ // Sync to preview org (fire and forget)
+ syncPreviewPricing(config);
+
+ // Return the tool result (no await to avoid deadlocks)
+ addToolOutput({
+ tool: "build_pricing",
+ toolCallId: toolCall.toolCallId,
+ output: {
+ success: true,
+ productsCount: config.products.length,
+ featuresCount: config.features.length,
+ },
+ });
+ }
},
- body: {
- sessionId: chatSessionIdRef.current,
- initialConfig: options?.initialConfig ?? null,
- },
- }),
-
- // Auto-submit when all tool results are available (for multi-step if needed)
- sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
-
- // Handle client-side tool execution
- onToolCall: async ({ toolCall }) => {
- // Check for dynamic tools first
- if (toolCall.dynamic) {
- return;
- }
-
- if (toolCall.toolName === "build_pricing") {
- const config = toolCall.input as AgentPricingConfig;
-
- // Update the pricing preview
- setPricingConfig(config);
-
- // Sync to preview org (fire and forget)
- syncPreviewPricing(config);
-
- // Return the tool result (no await to avoid deadlocks)
- addToolOutput({
- tool: "build_pricing",
- toolCallId: toolCall.toolCallId,
- output: {
- success: true,
- productsCount: config.products.length,
- featuresCount: config.features.length,
- },
- });
- }
},
- });
+ );
const handleSubmit = useCallback(
(message: PromptInputMessage) => {
diff --git a/vite/src/views/onboarding4/onboardingPrompts.ts b/vite/src/views/onboarding4/onboardingPrompts.ts
index 5b1aeaa7b..bbf3b8cf9 100644
--- a/vite/src/views/onboarding4/onboardingPrompts.ts
+++ b/vite/src/views/onboarding4/onboardingPrompts.ts
@@ -1,187 +1,29 @@
/**
* Prompts for the onboarding guide steps.
- * These are copied to clipboard when users click "Copy prompt".
- *
- * Edit the .md files in the prompts/ folder directly - no escaping needed!
- * Use {{PLACEHOLDER}} syntax for dynamic values.
+ * These are the CLI skill contents (which already include setup/config instructions).
+ * The single source of truth lives in packages/atmn/src/prompts/skills/.
*/
-import {
- type CreditSystemConfig,
- type Feature,
- FeatureType,
- type ProductV2,
- UsageModel,
-} from "@autumn/shared";
-import { useCallback, useMemo } from "react";
-import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
-import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
-import customerPrompt from "./prompts/customer.md?raw";
-import paymentsPrompt from "./prompts/payments.md?raw";
-import usagePrompt from "./prompts/usage.md?raw";
+import { autumnGatingContent, autumnSetupContent } from "atmn/skills";
+import { useCallback } from "react";
+
+function stripYamlFrontmatter({ content }: { content: string }): string {
+ return content.replace(/^---[\s\S]*?---\n*/, "");
+}
const ONBOARDING_PROMPTS: Record
= {
- customer: customerPrompt,
- payments: paymentsPrompt,
- usage: usagePrompt,
+ customer: stripYamlFrontmatter({ content: autumnSetupContent }),
+ usage: stripYamlFrontmatter({ content: autumnGatingContent }),
};
-/** Check if any product has prepaid items */
-function hasPrepaidItems({ products }: { products: ProductV2[] }): boolean {
- return products.some((p) =>
- p.items.some((item) => item.usage_model === UsageModel.Prepaid),
- );
-}
-
-/** Check if any feature is a credit system */
-function hasCreditSystem({ features }: { features: Feature[] }): boolean {
- return features.some((f) => f.type === FeatureType.CreditSystem);
-}
-
-function buildAutumnConfig({
- products,
- features,
-}: {
- products: ProductV2[];
- features: Feature[];
-}): string {
- if (products.length === 0 && features.length === 0) {
- return "(No products or features created yet)";
- }
-
- const config = {
- products: products.map((p) => ({
- id: p.id,
- name: p.name,
- is_add_on: p.is_add_on,
- is_default: p.is_default,
- group: p.group,
- free_trial: p.free_trial,
- items: p.items.map((item) => {
- const mappedItem: Record = {};
-
- // Always include type if present
- if (item.type) mappedItem.type = item.type;
-
- // Feature fields
- if (item.feature_id !== undefined)
- mappedItem.feature_id = item.feature_id;
- if (item.feature_type) mappedItem.feature_type = item.feature_type;
- if (item.included_usage !== undefined)
- mappedItem.included_usage = item.included_usage;
-
- // Price fields
- if (item.price !== undefined) mappedItem.price = item.price;
- if (item.tiers && item.tiers.length > 0) mappedItem.tiers = item.tiers;
- if (item.usage_model) mappedItem.usage_model = item.usage_model;
- if (item.billing_units) mappedItem.billing_units = item.billing_units;
-
- // Interval
- if (item.interval !== undefined) mappedItem.interval = item.interval;
-
- return mappedItem;
- }),
- })),
- features: features.map((f) => {
- const mappedFeature: Record = {
- id: f.id,
- name: f.name,
- type: f.type,
- };
-
- // Include credit schema for credit system features
- if (f.type === FeatureType.CreditSystem && f.config) {
- const config = f.config as CreditSystemConfig;
- if (config.schema && config.schema.length > 0) {
- mappedFeature.credit_schema = config.schema.map((item) => ({
- metered_feature_id: item.metered_feature_id,
- credit_amount: item.credit_amount,
- }));
- }
- }
-
- return mappedFeature;
- }),
- };
-
- return "```json\n" + JSON.stringify(config, null, 2) + "\n```";
-}
-
-// Prepaid options snippets
-const TS_OPTIONS_COMMENT = `
- // Optional: For prepaid pricing, specify quantities
- // options: [{ feature_id: "feature_id", quantity: 100 }]`;
-
-const PY_OPTIONS_COMMENT = `
- # Optional: For prepaid pricing, specify quantities
- # options=[{"feature_id": "feature_id", "quantity": 100}]`;
-
-const PREPAID_SECTION = `
-### Prepaid Pricing
-
-If the product has items with \`usage_model: "prepaid"\`, pass the \`options\` array to specify quantities:
-
-\`\`\`typescript
-const { data } = await autumn.checkout({
- customer_id: "user_123",
- product_id: "credits_pack",
- options: [{ feature_id: "credits", quantity: 500 }]
-});
-\`\`\`
-`;
-
-const CREDIT_SYSTEM_NOTE = `
-**Credit Systems:** You should only check and track with the underlying metered features (see \`credit_schema\` in the configuration), not the credit system itself. Autumn will automatically map usage and deduct the correct credit amount.
-`;
-
/**
- * Hook to get onboarding prompts with dynamic values populated.
+ * Hook to get onboarding prompts for clipboard copy.
*/
export function useOnboardingPrompt() {
- const { products } = useProductsQuery();
- const { features } = useFeaturesQuery();
-
- const autumnConfig = useMemo(
- () => buildAutumnConfig({ products, features }),
- [products, features],
- );
-
- const hasPrepaid = hasPrepaidItems({ products });
- const hasCredits = hasCreditSystem({ features });
-
const getPrompt = useCallback(
- ({ stepId }: { stepId: string }): string => {
- let prompt = ONBOARDING_PROMPTS[stepId] ?? "";
-
- // Replace dynamic placeholders
- prompt = prompt.replace("{{AUTUMN_CONFIG}}", autumnConfig);
-
- // Replace prepaid options placeholders
- if (hasPrepaid) {
- prompt = prompt.replace("{{TS_CHECKOUT_OPTIONS}}", TS_OPTIONS_COMMENT);
- prompt = prompt.replace("{{TS_ATTACH_OPTIONS}}", TS_OPTIONS_COMMENT);
- prompt = prompt.replace("{{PY_CHECKOUT_OPTIONS}}", PY_OPTIONS_COMMENT);
- prompt = prompt.replace("{{PY_ATTACH_OPTIONS}}", PY_OPTIONS_COMMENT);
- prompt = prompt.replace("{{PREPAID_SECTION}}", PREPAID_SECTION);
- } else {
- // Remove placeholders if no prepaid items
- prompt = prompt.replace("{{TS_CHECKOUT_OPTIONS}}", "");
- prompt = prompt.replace("{{TS_ATTACH_OPTIONS}}", "");
- prompt = prompt.replace("{{PY_CHECKOUT_OPTIONS}}", "");
- prompt = prompt.replace("{{PY_ATTACH_OPTIONS}}", "");
- prompt = prompt.replace("{{PREPAID_SECTION}}", "");
- }
-
- // Replace credit system note placeholder
- if (hasCredits) {
- prompt = prompt.replace("{{CREDIT_SYSTEM_NOTE}}", CREDIT_SYSTEM_NOTE);
- } else {
- prompt = prompt.replace("{{CREDIT_SYSTEM_NOTE}}", "");
- }
-
- return prompt;
- },
- [autumnConfig, hasPrepaid, hasCredits],
+ ({ stepId }: { stepId: string }): string =>
+ ONBOARDING_PROMPTS[stepId] ?? "",
+ [],
);
return { getPrompt };
diff --git a/vite/src/views/onboarding4/prompts/customer.md b/vite/src/views/onboarding4/prompts/customer.md
deleted file mode 100644
index 28c55503b..000000000
--- a/vite/src/views/onboarding4/prompts/customer.md
+++ /dev/null
@@ -1,294 +0,0 @@
-## Set up Autumn billing integration
-
-Autumn is a billing and entitlements layer over Stripe, which we are adding into this codebase. Your task is to create an Autumn customer by following these steps, and add it to a place in this app where it will be automatically created.
-
-### Step 1: Analyze my codebase
-
-Before making changes, detect:
-- Language (TypeScript/JavaScript, Python, or other)
-- If TS/JS: Framework (Next.js, React Router, Tanstack Start, Hono, Express, Fastify, or other)
-- If TS/JS: Is there a React frontend? (Check for React in package.json)
-
-Also ask me:
-
-**1. Should Autumn customers be individual users, or organizations?**
-- Users (B2C): Each user has their own plan and limits
-- Organizations (B2B): Plans and limits are shared across an org
-
-**2. Have you created an AUTUMN_SECRET_KEY and added it to .env?**
-Please prompt them to create one here: https://app.useautumn.com/dev?tab=api_keys and add it to .env as AUTUMN_SECRET_KEY
-
-
-
-Tell me what you detected, which path you'll follow and what you'll be adding autumn to.
-
----
-
-## Path A: React + Node.js (fullstack TypeScript)
-
-Use this path if there's a React frontend with a Node.js backend.
-
-### A1. Install the SDK
-
-**Use the package manager already installed** -- eg user may be using bun, or pnpm.
-```bash
-npm install autumn-js
-```
-
-### A2. Mount the handler (server-side)
-
-This creates endpoints at `/api/autumn/*` that the React hooks will call. The `identify` function should return either the user ID or org ID from your auth provider, depending on how you're using Autumn.
-
-**Next.js (App Router):**
-```typescript
-// app/api/autumn/[...all]/route.ts
-import { autumnHandler } from "autumn-js/next";
-
-export const { GET, POST } = autumnHandler({
- identify: async (request) => {
- // Get user/org from your auth provider
- const session = await auth.api.getSession({ headers: request.headers });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: {
- name: session?.user.name,
- email: session?.user.email,
- },
- };
- },
-});
-```
-
-**React Router:**
-```typescript
-// app/routes/api.autumn.tsx
-import { autumnHandler } from "autumn-js/react-router";
-
-export const { loader, action } = autumnHandler({
- identify: async (args) => {
- const session = await auth.api.getSession({ headers: args.request.headers });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: { name: session?.user.name, email: session?.user.email },
- };
- },
-});
-
-// routes.ts - add this route
-route("api/autumn/*", "routes/api.autumn.tsx")
-```
-
-**Tanstack Start:**
-```typescript
-// routes/api/autumn.$.ts
-import { autumnHandler } from "autumn-js/tanstack";
-
-const handler = autumnHandler({
- identify: async ({ request }) => {
- const session = await auth.api.getSession({ headers: request.headers });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: { name: session?.user.name, email: session?.user.email },
- };
- },
-});
-
-export const Route = createFileRoute("/api/autumn/$")({
- server: { handlers: handler },
-});
-```
-
-**Hono:**
-```typescript
-import { autumnHandler } from "autumn-js/hono";
-
-app.use("/api/autumn/*", autumnHandler({
- identify: async (c) => {
- const session = await auth.api.getSession({ headers: c.req.raw.headers });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: { name: session?.user.name, email: session?.user.email },
- };
- },
-}));
-```
-
-**Express:**
-```typescript
-import { autumnHandler } from "autumn-js/express";
-
-app.use(express.json()); // Must be before autumnHandler
-app.use("/api/autumn", autumnHandler({
- identify: async (req) => {
- const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers) });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: { name: session?.user.name, email: session?.user.email },
- };
- },
-}));
-```
-
-**Fastify:**
-```typescript
-import { autumnHandler } from "autumn-js/fastify";
-
-fastify.route({
- method: ["GET", "POST"],
- url: "/api/autumn/*",
- handler: autumnHandler({
- identify: async (request) => {
- const session = await auth.api.getSession({ headers: request.headers as any });
- return {
- customerId: session?.user.id, // or session?.org.id for B2B
- customerData: { name: session?.user.name, email: session?.user.email },
- };
- },
- }),
-});
-```
-
-**Other frameworks (generic handler):**
-```typescript
-import { autumnHandler } from "autumn-js/backend";
-
-// Mount this handler onto the /api/autumn/* path in your backend
-const handleRequest = async (request) => {
- // Your authentication logic here
- const customerId = "user_or_org_id_from_auth";
-
- let body = null;
- if (request.method !== "GET") {
- body = await request.json();
- }
-
- const { statusCode, response } = await autumnHandler({
- customerId,
- customerData: { name: "", email: "" },
- request: {
- url: request.url,
- method: request.method,
- body: body,
- },
- });
-
- return new Response(JSON.stringify(response), {
- status: statusCode,
- headers: { "Content-Type": "application/json" },
- });
-};
-```
-
-### A3. Add the provider (client-side)
-
-Wrap your app with AutumnProvider:
-```tsx
-import { AutumnProvider } from "autumn-js/react";
-
-export default function RootLayout({ children }) {
- return (
-
- {children}
-
- );
-}
-```
-
-If your backend is on a different URL (e.g., Vite + separate server), pass `backendUrl`:
-```tsx
-
-```
-
-### A4. Create a test customer
-
-Add this hook to any component to verify the integration:
-```tsx
-import { useCustomer } from "autumn-js/react";
-
-const { customer } = useCustomer();
-console.log("Autumn customer:", customer);
-```
-
-This automatically creates an Autumn customer for new users/orgs.
-
----
-
-## Path B: Backend only (Node.js, Python, or other)
-
-Use this path if there's no React frontend, or you prefer server-side only.
-
-### B1. Install the SDK
-```bash
-# Node.js
-npm install autumn-js
-
-# Python
-pip install autumn-py
-```
-
-### B2. Initialize the client
-
-**TypeScript/JavaScript:**
-```typescript
-import { Autumn } from "autumn-js";
-
-const autumn = new Autumn({
- secretKey: process.env.AUTUMN_SECRET_KEY,
-});
-```
-
-**Python:**
-```python
-from autumn import Autumn
-
-autumn = Autumn('am_sk_test_xxx')
-```
-
-### B3. Create a test customer
-
-This will GET or CREATE a new customer. Add it when a user signs in or loads the app. Pass in ID from auth provider.
-The response returns customer state, used to display billing information client-side. Please console.log the Autumn customer client-side.
-
-**TypeScript:**
-```typescript
-const { data, error } = await autumn.customers.create({
- id: "user_or_org_id_from_auth",
- name: "Test User",
- email: "test@example.com",
-});
-```
-
-**Python:**
-```python
-customer = await autumn.customers.create(
- id="user_or_org_id_from_auth",
- name="Test User",
- email="test@example.com",
-)
-```
-
-**cURL:**
-```bash
-curl -X POST https://api.useautumn.com/customers \
- -H "Authorization: Bearer am_sk_test_xxx" \
- -H "Content-Type: application/json" \
- -d '{"id": "user_or_org_id_from_auth", "name": "Test User", "email": "test@example.com"}'
-```
-
-When calling these functions from the client, the SDK exports types for all response objects. Use these for type-safe code.
-
-```tsx
-import type { Customer } from "autumn-js";
-```
-
----
-
-## Verify
-
-After setup, tell me:
-1. What stack you detected
-2. Which path you followed
-3. What files you created/modified
-4. That the Autumn customer is logged in browser, and to check in the Autumn dashboard
-
-Docs: https://docs.useautumn.com/llms.txt
\ No newline at end of file
diff --git a/vite/src/views/onboarding4/prompts/payments.md b/vite/src/views/onboarding4/prompts/payments.md
deleted file mode 100644
index 3555558b6..000000000
--- a/vite/src/views/onboarding4/prompts/payments.md
+++ /dev/null
@@ -1,248 +0,0 @@
-## Add Autumn payment flow
-
-Autumn handles Stripe checkout and plan changes. Your task is to add the payment flow to this codebase for ALL plans in the Autumn configuration.
-
-### Step 1: Detect my integration type
-
-Check if this codebase already has Autumn set up:
-- If there's an `AutumnProvider` and `autumnHandler` mounted → **Path A: React**
-- If there's just an `Autumn` client initialized → **Path B: Backend SDK**
-
-Before implementing:
-1. Tell me which path you'll follow before proceeding.
-2. Tell me that I will be building pricing cards to handle billing flows, and ask for any guidance or any input
-
----
-
-## Path A: React
-
-### Checkout Flow
-
-Use `checkout` from `useCustomer`. It returns either a Stripe URL (new customer) or checkout preview data (returning customer with card on file).
-
-```tsx
-import { useCustomer } from "autumn-js/react";
-
-const { checkout } = useCustomer();
-
-const data = await checkout({ productId: "pro" });
-
-if (!data.url) {
- // Returning customer → show confirmation dialog with result data
- // data contains: { product, current_product, lines, total (IN MAJOR CURRENCY), currency, next_cycle }
-}
-```
-
-After user confirms in your dialog, call `attach` to enable plan (and charge card as needed)
-
-```tsx
-const { attach } = useCustomer();
-
-await attach({ productId: "pro" });
-```
-
-### Getting Billing State
-
-Use `usePricingTable` to get products with their billing scenario and display state.
-
-```tsx
-import { usePricingTable } from "autumn-js/react";
-
-function PricingPage() {
- const { products } = usePricingTable();
- // Each product has: scenario, properties
- // scenario: "scheduled" | "active" | "new" | "renew" | "upgrade" | "downgrade" | "cancel"
-}
-```
-
-### Canceling
-Only use this if there is no free plan in the user's Autumn config. If there is a free plan, then you can cancel by attaching the free plan.
-
-```tsx
-const { cancel } = useCustomer();
-await cancel({ productId: "pro" });
-```
-
----
-
-## Path B: Backend SDK
-
-### Checkout Flow
-
-Payments are a 2-step process:
-1. **checkout** - Returns Stripe checkout URL (new customer) or preview data (returning customer)
-2. **attach** - Confirms purchase when no URL was returned
-
-**TypeScript:**
-```typescript
-import { Autumn } from "autumn-js";
-import type { CheckoutResult, AttachResult } from "autumn-js";
-
-const autumn = new Autumn({ secretKey: process.env.AUTUMN_SECRET_KEY });
-
-// Step 1: Get checkout info
-const { data } = await autumn.checkout({
- customer_id: "user_or_org_id_from_auth",
- product_id: "pro",{{TS_CHECKOUT_OPTIONS}}
-}) as { data: CheckoutResult };
-
-if (data.url) {
- // New customer → redirect to Stripe
- return redirect(data.url);
-} else {
- // Returning customer → return preview data for confirmation UI
- // data contains: { product, current_product, lines, total (IN MAJOR CURRENCY), currency, next_cycle }
- return data;
-}
-
-// Step 2: After user confirms (only if no URL)
-const { data: attachData } = await autumn.attach({
- customer_id: "user_or_org_id_from_auth",
- product_id: "pro",{{TS_ATTACH_OPTIONS}}
-}) as { data: AttachResult };
-```
-
-**Python:**
-```python
-from autumn import Autumn
-
-autumn = Autumn('am_sk_test_xxx')
-
-# Step 1: Get checkout info
-response = await autumn.checkout(
- customer_id="user_or_org_id_from_auth",
- product_id="pro",{{PY_CHECKOUT_OPTIONS}}
-)
-
-if response.url:
- # New customer → redirect to Stripe
- return redirect(response.url)
-else:
- # Returning customer → return preview data for confirmation UI
- return response
-
-# Step 2: After user confirms
-attach_response = await autumn.attach(
- customer_id="user_or_org_id_from_auth",
- product_id="pro",{{PY_ATTACH_OPTIONS}}
-)
-```
-{{PREPAID_SECTION}}
-
-### Getting Billing State
-
-Use `products.list` with a `customer_id` to get products with their billing scenario. **Don't build custom billing state logic.**
-
-**TypeScript:**
-```typescript
-const { data } = await autumn.products.list({
- customer_id: "user_or_org_id_from_auth",
-});
-
-data.list.forEach((product) => {
- const { scenario } = product;
- // "scheduled" | "active" | "new" | "renew" | "upgrade" | "downgrade" | "cancel"
-});
-```
-
-**Python:**
-```python
-response = await autumn.products.list(customer_id="user_or_org_id_from_auth")
-
-for product in response.list:
- scenario = product.scenario
-```
-
-**curl:**
-```bash
-curl https://api.useautumn.com/v1/products?customer_id=user_or_org_id_from_auth \
- -H "Authorization: Bearer $AUTUMN_SECRET_KEY"
-```
-
-### Canceling
-
-```typescript
-await autumn.cancel({ customer_id: "...", product_id: "pro" });
-```
-
-Or attach a free product ID to downgrade.
-
----
-
-## Common Patterns
-
-### Pricing Button Text
-
-```typescript
-const SCENARIO_TEXT: Record = {
- scheduled: "Plan Scheduled",
- active: "Current Plan",
- renew: "Renew",
- upgrade: "Upgrade",
- new: "Enable",
- downgrade: "Downgrade",
- cancel: "Cancel Plan",
-};
-
-export const getPricingButtonText = (product: Product): string => {
- const { scenario, properties } = product;
- const { is_one_off, updateable, has_trial } = properties ?? {};
-
- if (has_trial) return "Start Trial";
- if (scenario === "active" && updateable) return "Update";
- if (scenario === "new" && is_one_off) return "Purchase";
-
- return SCENARIO_TEXT[scenario ?? ""] ?? "Enable Plan";
-};
-```
-
-### Confirmation Dialog Text
-
-```typescript
-import type { CheckoutResult, Product } from "autumn-js";
-
-export const getConfirmationTexts = (result: CheckoutResult): { title: string; message: string } => {
- const { product, current_product, next_cycle } = result;
- const scenario = product.scenario;
- const productName = product.name;
- const currentProductName = current_product?.name;
- const nextCycleDate = next_cycle?.starts_at
- ? new Date(next_cycle.starts_at).toLocaleDateString()
- : undefined;
-
- const isRecurring = !product.properties?.is_one_off;
-
- const CONFIRMATION_TEXT: Record = {
- scheduled: { title: "Already Scheduled", message: "You already have this product scheduled." },
- active: { title: "Already Active", message: "You are already subscribed to this product." },
- renew: { title: "Renew", message: `Renew your subscription to ${productName}.` },
- upgrade: { title: `Upgrade to ${productName}`, message: `Upgrade to ${productName}. Your card will be charged immediately.` },
- downgrade: { title: `Downgrade to ${productName}`, message: `${currentProductName} will be cancelled. ${productName} begins ${nextCycleDate}.` },
- cancel: { title: "Cancel", message: `Your subscription to ${currentProductName} will end ${nextCycleDate}.` },
- };
-
- if (scenario === "new") {
- return isRecurring
- ? { title: `Subscribe to ${productName}`, message: `Subscribe to ${productName}. Charged immediately.` }
- : { title: `Purchase ${productName}`, message: `Purchase ${productName}. Charged immediately.` };
- }
-
- return CONFIRMATION_TEXT[scenario ?? ""] ?? { title: "Change Subscription", message: "You are about to change your subscription." };
-};
-```
-
----
-
-## Notes
-
-- **NB: the result is `data.url`, NOT `data.checkout_url`**
-- This handles all upgrades, downgrades, renewals, uncancellations automatically
-- Product IDs come from the Autumn configuration (below)
-- Pass `successUrl` to `checkout` to redirect users after payment
-
-Docs: https://docs.useautumn.com/llms.txt
-
----
-
-## Current Autumn Configuration
-{{AUTUMN_CONFIG}}
diff --git a/vite/src/views/onboarding4/prompts/usage.md b/vite/src/views/onboarding4/prompts/usage.md
deleted file mode 100644
index c4b60b46d..000000000
--- a/vite/src/views/onboarding4/prompts/usage.md
+++ /dev/null
@@ -1,125 +0,0 @@
-## Add Autumn gating and usage tracking
-
-Autumn tracks feature usage and enforces limits. Add usage tracking to this codebase.
-
-### Step 1: Detect my integration type
-
-Check if this codebase already has Autumn set up:
-- If there's an `AutumnProvider` and `autumnHandler` mounted → **React hooks available** (can use for UX)
-- Backend SDK should **always** be used to enforce limits server-side
-
-Tell me what you detected before proceeding.
-
----
-
-## Frontend checks (React hooks)
-
-Use frontend checks for **UX only** - showing/hiding features, prompting upgrades. These should NOT be trusted for security.
-
-### Check feature access
-```tsx
-import { useCustomer } from "autumn-js/react";
-
-export function SendChatMessage() {
- const { check, refetch } = useCustomer();
-
- const handleSendMessage = async () => {
- const { data } = check({ featureId: "messages" });
-
- if (!data?.allowed) {
- alert("You're out of messages");
- } else {
- //send chatbot message
- //then, refresh customer usage data
- await refetch();
- }
- };
-}
-```
-
----
-
-## Backend checks (required for security)
-
-**Always check on the backend** before executing any protected action. Frontend checks can be bypassed.
-
-### TypeScript
-```typescript
-import { Autumn } from "autumn-js";
-
-const autumn = new Autumn({
- secretKey: process.env.AUTUMN_SECRET_KEY,
-});
-
-// Check before executing the action
-const { data } = await autumn.check({
- customer_id: "user_or_org_id_from_auth",
- feature_id: "api_calls",
-});
-
-if (!data.allowed) {
- return { error: "Usage limit reached" };
-}
-
-// Safe to proceed - do the actual work here
-const result = await doTheActualWork();
-
-// Track usage after success
-await autumn.track({
- customer_id: "user_or_org_id_from_auth",
- feature_id: "api_calls",
- value: 1,
-});
-
-return result;
-```
-
-### Python
-```python
-from autumn import Autumn
-
-autumn = Autumn('am_sk_test_xxx')
-
-# Check before executing the action
-response = await autumn.check(
- customer_id="user_or_org_id_from_auth",
- feature_id="api_calls"
-)
-
-if not response.allowed:
- raise HTTPException(status_code=403, detail="Usage limit reached")
-
-# Safe to proceed - do the actual work here
-result = await do_the_actual_work()
-
-# Track usage after success
-await autumn.track(
- customer_id="user_or_org_id_from_auth",
- feature_id="api_calls",
- value=1
-)
-
-return result
-```
-
----
-
-## Notes
-
-- **Frontend checks** = UX (show/hide UI, display limits) - can be bypassed by users
-- **Backend checks** = Security (enforce limits) - required before any protected action
-- Pattern: check → do work → track (only track after successful completion)
-- Feature IDs come from the Autumn configuration (below)
-- Current usage and total limit can be taken from from Customer object and displayed -- see the Customer types from the Autumn SDK
-```tsx
-import type { Customer } from "autumn-js";
-
-//Balance is: customer.features..balance
-```
-{{CREDIT_SYSTEM_NOTE}}
-Docs: https://docs.useautumn.com/llms.txt
-
----
-
-## Current Autumn Configuration
-{{AUTUMN_CONFIG}}
diff --git a/vite/tsconfig.json b/vite/tsconfig.json
index 8c900412f..d2665cbba 100644
--- a/vite/tsconfig.json
+++ b/vite/tsconfig.json
@@ -9,7 +9,8 @@
"paths": {
"@/*": ["./src/*"],
"autumn-js": ["../packages/autumn-js/src/sdk/index.ts"],
- "autumn-js/react": ["../packages/autumn-js/src/react/index.ts"]
+ "autumn-js/react": ["../packages/autumn-js/src/react/index.ts"],
+ "atmn/skills": ["../packages/atmn/src/prompts/skills/index.ts"]
}
// "noUnusedLocals": false
}
diff --git a/vite/vite.config.ts b/vite/vite.config.ts
index 9781689ba..e80af43c5 100644
--- a/vite/vite.config.ts
+++ b/vite/vite.config.ts
@@ -34,6 +34,10 @@ export default defineConfig({
__dirname,
"../packages/autumn-js/src/sdk/index.ts",
),
+ "atmn/skills": path.resolve(
+ __dirname,
+ "../packages/atmn/src/prompts/skills/index.ts",
+ ),
// Hide Radix UI imports with cleaner aliases
"@radix/accordion": "@radix-ui/react-accordion",
@@ -56,6 +60,7 @@ export default defineConfig({
// Exclude workspace dependencies from pre-bundling to avoid cache issues
exclude: [
"@autumn/shared",
+ "atmn/skills",
"autumn-js",
"autumn-js/react",
"better-auth",