AlbyCliSkill.tsx raw

   1  import { BotIcon, CopyIcon, ShieldCheckIcon, WalletIcon } from "lucide-react";
   2  import React from "react";
   3  import { toast } from "sonner";
   4  import { AppDetailConnectedApps } from "src/components/connections/AppDetailConnectedApps";
   5  import { AppStoreDetailHeader } from "src/components/connections/AppStoreDetailHeader";
   6  import { appStoreApps } from "src/components/connections/SuggestedAppData";
   7  import {
   8    Accordion,
   9    AccordionContent,
  10    AccordionItem,
  11    AccordionTrigger,
  12  } from "src/components/ui/accordion";
  13  import { Button } from "src/components/ui/button";
  14  import {
  15    Card,
  16    CardContent,
  17    CardHeader,
  18    CardTitle,
  19  } from "src/components/ui/card";
  20  import { LoadingButton } from "src/components/ui/custom/loading-button";
  21  import {
  22    DEFAULT_APP_BUDGET_RENEWAL,
  23    DEFAULT_APP_BUDGET_SATS,
  24  } from "src/constants";
  25  import { copyToClipboard } from "src/lib/clipboard";
  26  import { createApp } from "src/requests/createApp";
  27  import { handleRequestError } from "src/utils/handleRequestError";
  28  
  29  const skillInstallCommand = "npx skills add getAlby/payments-skill";
  30  const skillInstallPrompt =
  31    "Run `npx -y skills add getAlby/payments-skill -y` to install the Alby bitcoin payments skill";
  32  const verifyPrompt = "What's your wallet balance?";
  33  
  34  export function AlbyCliSkill() {
  35    const [isLoading, setLoading] = React.useState(false);
  36    const [connectionSecret, setConnectionSecret] = React.useState("");
  37  
  38    const appStoreApp = appStoreApps.find((app) => app.id === "alby-cli-skill");
  39    if (!appStoreApp) {
  40      return null;
  41    }
  42  
  43    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
  44      event.preventDefault();
  45      setLoading(true);
  46  
  47      try {
  48        const createAppResponse = await createApp({
  49          name: appStoreApp.title,
  50          scopes: [
  51            "get_info",
  52            "get_balance",
  53            "list_transactions",
  54            "lookup_invoice",
  55            "make_invoice",
  56            "notifications",
  57            "pay_invoice",
  58            "sign_message",
  59          ],
  60          maxAmountSat: DEFAULT_APP_BUDGET_SATS,
  61          budgetRenewal: DEFAULT_APP_BUDGET_RENEWAL,
  62          metadata: {
  63            app_store_app_id: "alby-cli-skill",
  64          },
  65        });
  66  
  67        setConnectionSecret(createAppResponse.pairingUri);
  68        toast("Alby CLI Skill connection created");
  69      } catch (error) {
  70        handleRequestError("Failed to create connection", error);
  71      } finally {
  72        setLoading(false);
  73      }
  74    };
  75  
  76    return (
  77      <div className="grid gap-5">
  78        <AppStoreDetailHeader appStoreApp={appStoreApp} contentRight={null} />
  79  
  80        {connectionSecret ? (
  81          <div className="max-w-3xl flex flex-col gap-5">
  82            <p className="text-muted-foreground">
  83              Follow these steps to install and use Alby CLI Skill with your AI
  84              agent.
  85            </p>
  86  
  87            <Accordion type="single" collapsible>
  88              <AccordionItem value="install">
  89                <AccordionTrigger>
  90                  Install with one command (Claude Code, Goose etc)
  91                </AccordionTrigger>
  92                <AccordionContent>
  93                  <ul className="list-decimal list-inside space-y-2 text-muted-foreground">
  94                    <li>
  95                      Install the Alby CLI skill:
  96                      <div className="my-4 flex items-center gap-4">
  97                        <div className="font-mono text-foreground text-sm break-all bg-muted p-2 rounded">
  98                          {skillInstallCommand}
  99                        </div>
 100                        <Button
 101                          size="sm"
 102                          onClick={() => copyToClipboard(skillInstallCommand)}
 103                        >
 104                          <CopyIcon />
 105                          Copy Command
 106                        </Button>
 107                      </div>
 108                    </li>
 109                    <li>
 110                      Save your wallet connection secret at
 111                      <code className="ml-1 text-foreground">
 112                        ~/.alby-cli/connection-secret.key
 113                      </code>
 114                      .
 115                    </li>
 116                  </ul>
 117  
 118                  <div className="mt-4 flex flex-col gap-2">
 119                    <div className="text-sm text-muted-foreground">
 120                      Connection secret
 121                    </div>
 122                    <div className="flex items-center gap-2">
 123                      <Button
 124                        size="sm"
 125                        onClick={() => copyToClipboard(connectionSecret)}
 126                      >
 127                        <CopyIcon />
 128                        Copy Connection secret
 129                      </Button>
 130                    </div>
 131                  </div>
 132                </AccordionContent>
 133              </AccordionItem>
 134  
 135              <AccordionItem value="openclaw">
 136                <AccordionTrigger>OpenClaw setup</AccordionTrigger>
 137                <AccordionContent>
 138                  <ul className="list-decimal list-inside space-y-2 text-muted-foreground">
 139                    <li>
 140                      Tell your agent to install this skill as a custom skill:
 141                      <div className="my-4 flex items-center gap-4">
 142                        <div className="font-mono text-foreground text-sm break-all bg-muted p-2 rounded">
 143                          {skillInstallPrompt}
 144                        </div>
 145                        <Button
 146                          size="sm"
 147                          onClick={() => copyToClipboard(skillInstallPrompt)}
 148                        >
 149                          <CopyIcon />
 150                          Copy Prompt
 151                        </Button>
 152                      </div>
 153                    </li>
 154                    <li>
 155                      Save your wallet connection secret at
 156                      <code className="ml-1 text-foreground">
 157                        ~/.alby-cli/connection-secret.key
 158                      </code>
 159                      .
 160                    </li>
 161                  </ul>
 162  
 163                  <div className="mt-4 flex flex-col gap-2">
 164                    <div className="text-sm text-muted-foreground">
 165                      Connection secret
 166                    </div>
 167                    <div className="flex items-center gap-2">
 168                      <Button
 169                        size="sm"
 170                        onClick={() => copyToClipboard(connectionSecret)}
 171                      >
 172                        <CopyIcon />
 173                        Copy Connection secret
 174                      </Button>
 175                    </div>
 176                  </div>
 177                </AccordionContent>
 178              </AccordionItem>
 179  
 180              <AccordionItem value="verify">
 181                <AccordionTrigger>Verify it works</AccordionTrigger>
 182                <AccordionContent>
 183                  <p className="text-muted-foreground">
 184                    Ask your agent this question:
 185                  </p>
 186                  <div className="mt-2 flex items-center gap-2">
 187                    <div className="font-mono text-foreground text-sm break-all bg-muted p-2 rounded">
 188                      {verifyPrompt}
 189                    </div>
 190                    <Button
 191                      size="sm"
 192                      onClick={() => copyToClipboard(verifyPrompt)}
 193                    >
 194                      <CopyIcon />
 195                      Copy prompt
 196                    </Button>
 197                  </div>
 198                </AccordionContent>
 199              </AccordionItem>
 200            </Accordion>
 201          </div>
 202        ) : (
 203          <>
 204            <Card className="max-w-lg">
 205              <CardHeader>
 206                <CardTitle className="text-2xl">About the App</CardTitle>
 207              </CardHeader>
 208              <CardContent className="flex flex-col gap-5">
 209                <p className="text-muted-foreground">
 210                  Alby CLI Skill is built for AI agents that need to use Alby CLI
 211                  more effectively. It connects your Alby Hub to skill-enabled
 212                  agents so they can use Bitcoin payments inside autonomous
 213                  workflows.
 214                </p>
 215  
 216                <ul className="text-muted-foreground pl-4 flex flex-col gap-3">
 217                  <li>
 218                    <WalletIcon className="size-4 inline" /> Give your AI agent a
 219                    programmable Lightning wallet to check balances, create
 220                    invoices, and send payments from prompts.
 221                  </li>
 222                  <li>
 223                    <BotIcon className="size-4 inline" /> Enable agentic payment
 224                    workflows in OpenClaw with wallet context for multi-step
 225                    tasks.
 226                  </li>
 227                  <li>
 228                    <ShieldCheckIcon className="size-4 inline" /> Use scoped NWC
 229                    permissions with budgets so you can grant access safely and
 230                    stay in control of spending.
 231                  </li>
 232                </ul>
 233  
 234                <form
 235                  onSubmit={handleSubmit}
 236                  className="flex flex-col items-start gap-5 max-w-lg"
 237                >
 238                  <LoadingButton loading={isLoading} type="submit">
 239                    Create Connection
 240                  </LoadingButton>
 241                </form>
 242              </CardContent>
 243            </Card>
 244            <AppDetailConnectedApps appStoreApp={appStoreApp} showTitle />
 245          </>
 246        )}
 247      </div>
 248    );
 249  }
 250