import { ArrowRightIcon, ArrowUpRightIcon, BotIcon, BoxIcon, CheckCircleIcon, ChevronRightIcon, CopyIcon, EyeOffIcon, HammerIcon, InfoIcon, LayersIcon, LayoutGridIcon, type LucideIcon, RepeatIcon, ShieldCheckIcon, ShoppingBagIcon, SparklesIcon, XIcon, ZapIcon, } from "lucide-react"; import React from "react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import bitrefillLogo from "src/assets/suggested-apps/bitrefill.png"; import claudeLogo from "src/assets/suggested-apps/claude.png"; import clineLogo from "src/assets/suggested-apps/cline.png"; import codexLogo from "src/assets/suggested-apps/codex.png"; import cursorLogo from "src/assets/suggested-apps/cursor.png"; import geminiLogo from "src/assets/suggested-apps/gemini.png"; import gooseLogo from "src/assets/suggested-apps/goose.png"; import hermesLogo from "src/assets/suggested-apps/hermes.png"; import openclawLogo from "src/assets/suggested-apps/openclaw.png"; import opencodeLogo from "src/assets/suggested-apps/opencode.png"; import payperqLogo from "src/assets/suggested-apps/payperq.png"; import piLogo from "src/assets/suggested-apps/pi.png"; import AppHeader from "src/components/AppHeader"; import ExternalLink from "src/components/ExternalLink"; import Loading from "src/components/Loading"; import { Button } from "src/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, } from "src/components/ui/card"; import { LinkButton } from "src/components/ui/custom/link-button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "src/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "src/components/ui/tabs"; import { DEFAULT_APP_BUDGET_RENEWAL, DEFAULT_APP_BUDGET_SATS, localStorageKeys, } from "src/constants"; import { useInfo } from "src/hooks/useInfo"; import { copyToClipboard } from "src/lib/clipboard"; import { createApp } from "src/requests/createApp"; import { handleRequestError } from "src/utils/handleRequestError"; type Agent = { id: string; name: string; logo: string; description: string; setupUrl: string; mcpInstructions?: () => React.ReactNode; }; const agents: Agent[] = [ { id: "openclaw", name: "OpenClaw", logo: openclawLogo, description: "Open-source personal AI assistant", setupUrl: "", }, { id: "hermes", name: "Hermes", logo: hermesLogo, description: "Self-improving open-source AI agent by Nous Research", setupUrl: "", }, { id: "cursor", name: "Cursor", logo: cursorLogo, description: "AI-powered code editor", setupUrl: "", }, { id: "claude", name: "Claude", logo: claudeLogo, description: "Anthropic's AI assistant (Code, Web & Desktop)", setupUrl: "", mcpInstructions: () => (
  1. Open{" "} claude.ai {" "} or Claude Desktop and sign in
  2. Go to Settings → Connectors
  3. Add custom connector
  4. Enter “Alby” as connector name
  5. Paste the URL below as the connector URL
), }, { id: "gemini", name: "Gemini CLI", logo: geminiLogo, description: "Google's open-source AI agent for the terminal", setupUrl: "", }, { id: "codex", name: "Codex", logo: codexLogo, description: "OpenAI's coding agent", setupUrl: "", }, { id: "cline", name: "Cline", logo: clineLogo, description: "AI coding assistant for VS Code", setupUrl: "", }, { id: "goose", name: "Goose", logo: gooseLogo, description: "Local AI agent by Block for automating engineering tasks", setupUrl: "", }, { id: "opencode", name: "OpenCode", logo: opencodeLogo, description: "Terminal-based AI coding assistant", setupUrl: "", }, { id: "pi", name: "Pi", logo: piLogo, description: "Minimal terminal coding agent, great for local LLMs", setupUrl: "", }, ]; export function AI() { const navigate = useNavigate(); const [selectedAgent, setSelectedAgent] = React.useState(null); const [heroDismissed, setHeroDismissed] = React.useState( () => localStorage.getItem(localStorageKeys.aiHeroDismissed) === "true" ); const dismissHero = React.useCallback(() => { setHeroDismissed(true); localStorage.setItem(localStorageKeys.aiHeroDismissed, "true"); }, []); const selectedAgentData = agents.find((a) => a.id === selectedAgent); const showAuthPrompt = selectedAgent && !selectedAgentData?.setupUrl; const handleConnect = () => { if (selectedAgentData?.setupUrl) { navigate(selectedAgentData.setupUrl); } }; return ( <> Explore App Store } /> {/* Hero — collapsible, persisted in localStorage */} {!heroDismissed && (
{/* Left */}

AI + Bitcoin

Give your AI agent
a wallet

Connect any AI agent to your Alby Hub and let it send payments, buy gift cards, and access paid services on its own.

{/* Right — terminal mockup */}
claude
> Buy a $15 DoorDash gift card

Searching Bitrefill for DoorDash...

Paying 45,210 sats via Lightning

Done!{" "} Gift card code: XXXX-XXXX-XXXX

> Send $5 to hub@getalby.com

Sending 15,000 sats...

Sent!{" "} Payment confirmed

>
{/* Why Lightning — value props */}
{whyLightningItems.map((item) => { const Icon = item.icon; return (

{item.title}

{item.description}

); })}
)} {/* Connect section */}
{selectedAgentData ? ( {selectedAgentData.name} ) : (
)}
Connect Your Agent

Pick your agent, create a connection, and follow the setup steps

Manage Connections
{selectedAgentData?.setupUrl && ( )}
{/* Auth prompt shown immediately for CLI agents */} {showAuthPrompt && ( )}
{/* Inspiration */} {/* Featured services — branded full cards */}

Featured Services

Bitrefill

Bitrefill

Gift cards & e-SIMs

"Buy a $15 DoorDash gift card" — your agent pays via Lightning and delivers the code.

MCP Server

PPQ.ai

PPQ.ai

AI model access

Pay-per-prompt access to top AI models. No subscription — pay with Lightning.

NWC Auto Top-up

402 Index

1,000+ APIs

Directory of paid API endpoints — search, data, compute, LLMs, and more.

L402 / x402 / MPP

); } function GenericAuthPrompt({ agent }: { agent: Agent }) { const [mcpMode, setMcpMode] = React.useState(false); const basePath = import.meta.env.BASE_URL.replace(/\/$/, ""); const hubUrl = window.location.origin + basePath; const genericPrompt = `Run \`npx -y skills add getAlby/payments-skill -y\` and connect to my Alby Hub wallet at ${hubUrl}`; return (
{mcpMode && agent.mcpInstructions ? ( setMcpMode(false)} /> ) : ( <>

Copy this prompt and paste it into {agent.name}:

{agent.mcpInstructions && (

)} )}
); } function McpSetup({ agentName, mcpInstructions, onBack, }: { agentName: string; mcpInstructions: () => React.ReactNode; onBack: () => void; }) { const [isLoading, setIsLoading] = React.useState(false); const [mcpUrl, setMcpUrl] = React.useState(""); const [createdAppId, setCreatedAppId] = React.useState(); const handleCreateConnection = React.useCallback(async () => { if (isLoading || mcpUrl) { return; } setIsLoading(true); try { const response = await createApp({ name: agentName, scopes: [ "get_info", "get_balance", "list_transactions", "lookup_invoice", "make_invoice", "notifications", "pay_invoice", "sign_message", ], maxAmountSat: DEFAULT_APP_BUDGET_SATS, budgetRenewal: DEFAULT_APP_BUDGET_RENEWAL, metadata: { app_store_app_id: agentName.toLowerCase().replace(/\s+/g, "-"), }, }); setMcpUrl( `https://mcp.getalby.com/mcp?nwc=${encodeURIComponent(response.pairingUri)}` ); setCreatedAppId(response.id); toast("Connection created"); } catch (error) { handleRequestError("Failed to create connection", error); } setIsLoading(false); }, [agentName, isLoading, mcpUrl]); // Create connection immediately when the component mounts React.useEffect(() => { handleCreateConnection(); }, [handleCreateConnection]); if (isLoading) { return (
Creating connection...
); } if (!mcpUrl) { return ( ); } const maskedUrl = `https://mcp.getalby.com/mcp?nwc=${"•".repeat(20)}`; return (
{mcpInstructions()}
{createdAppId && ( Edit budget & permissions )}
); } const whyLightningItems = [ { icon: ShieldCheckIcon, title: "Stay in Control", description: "Set spending limits per agent. You decide how much it can spend and when budgets reset — no surprise bills.", }, { icon: EyeOffIcon, title: "Private by Default", description: "No credit cards or personal info shared with merchants. Your agent pays over lightning — fast, direct, and private.", }, { icon: ZapIcon, title: "Instant Access to Paid Services", description: "Your agent can access 1,000+ paid APIs instantly — gift cards, domains, hosting, AI models, and more.", }, ]; function getInspirationCategories(hasChannelManagement: boolean): { label: string; icon: LucideIcon; prompts: string[]; skill?: { prompt: string; skillName: string; url: string }; }[] { return [ { label: "Wallet", icon: ZapIcon, prompts: [ "send $5 to hub@getalby.com for coffee", "how much is $10 in sats right now?", "make an invoice for 50,000 sats", ], }, { label: "Shopping", icon: ShoppingBagIcon, prompts: [ "buy a $25 Netflix gift card on bitrefill.com", "get me an eSIM with 5GB of data for my trip to Portugal on bitrefill.com", "what gift cards are available in the US on bitrefill.com?", ], skill: { prompt: "Run `npx -y skills add bitrefill/agents -y` to install the Bitrefill Skill", skillName: "Bitrefill Skill", url: "https://bitrefill.com/agents", }, }, { label: "Creative", icon: SparklesIcon, prompts: [ "generate a watercolor painting of a mountain cabin at sunset on ppq.ai", "generate a cool bitcoin logo on ppq.ai and print it on a t-shirt on unhuman.store", "create a logo for my coffee shop using ppq.ai image generation", ], }, { label: "Services", icon: LayersIcon, prompts: [ "search podcasts for discussions about bitcoin scaling", "set up an anonymous email address on lnemail.net", "buy the domain my-awesome-project.dev on unhuman.domains", "spin up a VPS with 2 cores and 4GB RAM on lnvps.net", ], }, { label: "Automation", icon: RepeatIcon, prompts: [ "read payouts.csv and send 1,000 sats to each lightning address", "calculate how much I spent this month and break it down by day", "export all my transactions from the last 12 months as a CSV", ], }, { label: "Build Apps", icon: HammerIcon, prompts: [ "build an AI image generator that charges 500 sats per image", "create a file converter that charges 50 sats per conversion", "build a blog where readers unlock articles for 50 sats each", ], skill: { prompt: "Run `npx -y skills add getAlby/builder-skill -y` to install the Builder Skill", skillName: "Builder Skill", url: "https://github.com/getAlby/builder-skill", }, }, { label: "Alby Hub", icon: BoxIcon, prompts: [ "create a sub-wallet for my mum", "setup a new alby hub on my VPS", "give me an on-chain deposit address", "create a new app connection with a 21,000 sat monthly budget", "list all my app connections and their budgets", "make a read-only connection I can share with my accountant", 'revoke the connection called "old laptop"', ...(hasChannelManagement ? [ "open a channel with 2M sats to ACINQ's node", "show me my channels and their balances", "what's my node's connection info?", ] : []), ], skill: { prompt: "Run `npx -y skills add getAlby/hub-skill -y` to install the Alby Hub Skill", skillName: "Alby Hub Skill", url: "https://github.com/getAlby/hub-skill", }, }, ]; } function RotatingPrompt({ prompts }: { prompts: string[] }) { const [index, setIndex] = React.useState(0); const [charCount, setCharCount] = React.useState(0); const [isTyping, setIsTyping] = React.useState(true); const currentPrompt = prompts[index]; // Reset when prompts change (tab switch) React.useEffect(() => { setIndex(0); setCharCount(0); setIsTyping(true); }, [prompts]); // Typing effect — advance characters React.useEffect(() => { if (!isTyping) { return; } if (charCount >= currentPrompt.length) { setIsTyping(false); return; } const timeout = setTimeout(() => { setCharCount((c) => c + 1); }, 30); return () => clearTimeout(timeout); }, [charCount, currentPrompt, isTyping]); // Pause then rotate to next prompt React.useEffect(() => { if (isTyping) { return; } const pause = setTimeout(() => { setIndex((i) => (i + 1) % prompts.length); setCharCount(0); setIsTyping(true); }, 3000); return () => clearTimeout(pause); }, [isTyping, prompts]); return ( ); } function InspirationPrompts() { const { hasChannelManagement } = useInfo(); const inspirationCategories = getInspirationCategories(!!hasChannelManagement); return (

What can your agent do?

{inspirationCategories.map((cat) => { const Icon = cat.icon; return ( {cat.label} ); })}
{inspirationCategories.map((cat) => ( {cat.skill && (
Requires the{" "} {cat.skill.skillName} {" "} — ask your agent:{" "} “{cat.skill.prompt}”
)}
))}
); }