From 1fa264c9ccbe389fb54785cd1eb23d6fc889cf6a Mon Sep 17 00:00:00 2001 From: Alex Baldwin Date: Mon, 6 Jul 2026 10:49:37 -0400 Subject: [PATCH 1/4] Add the Logo.dev shadcn/ui component registry Six installable items (logo, logo-avatar, brand-search, logo-wall, attribution, logo-lib) with sources in registry/new-york/, built JSON committed in r/, and CI enforcing typecheck/lint/test plus build-output sync. components/ui/ holds vendored shadcn primitives for typechecking only. MIT license added (repo previously had none). Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 23 +- .gitignore | 3 + README.md | 45 + biome.jsonc | 43 + components.json | 20 + components/ui/avatar.tsx | 50 + components/ui/button.tsx | 57 + components/ui/command.tsx | 148 + components/ui/dialog.tsx | 122 + components/ui/popover.tsx | 33 + lib/utils.ts | 6 + package.json | 33 + pnpm-lock.yaml | 4702 +++++++++++++++++ r/attribution.json | 22 + r/brand-search.json | 41 + r/logo-avatar.json | 26 + r/logo-lib.json | 23 + r/logo-wall.json | 27 + r/logo.json | 26 + r/registry.json | 131 + registry.json | 131 + .../blocks/brand-search/brand-search.tsx | 186 + .../new-york/blocks/brand-search/route.ts | 63 + .../new-york/blocks/logo-wall/logo-wall.tsx | 99 + registry/new-york/hooks/use-brand-search.ts | 105 + registry/new-york/lib/logo-dev.test.ts | 151 + registry/new-york/lib/logo-dev.ts | 218 + registry/new-york/ui/attribution.tsx | 38 + registry/new-york/ui/logo-avatar.tsx | 71 + registry/new-york/ui/logo.tsx | 289 + styles/globals.css | 6 + tsconfig.json | 20 + vitest.config.ts | 7 + 33 files changed, 6962 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 biome.jsonc create mode 100644 components.json create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/popover.tsx create mode 100644 lib/utils.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 r/attribution.json create mode 100644 r/brand-search.json create mode 100644 r/logo-avatar.json create mode 100644 r/logo-lib.json create mode 100644 r/logo-wall.json create mode 100644 r/logo.json create mode 100644 r/registry.json create mode 100644 registry.json create mode 100644 registry/new-york/blocks/brand-search/brand-search.tsx create mode 100644 registry/new-york/blocks/brand-search/route.ts create mode 100644 registry/new-york/blocks/logo-wall/logo-wall.tsx create mode 100644 registry/new-york/hooks/use-brand-search.ts create mode 100644 registry/new-york/lib/logo-dev.test.ts create mode 100644 registry/new-york/lib/logo-dev.ts create mode 100644 registry/new-york/ui/attribution.tsx create mode 100644 registry/new-york/ui/logo-avatar.tsx create mode 100644 registry/new-york/ui/logo.tsx create mode 100644 styles/globals.css create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecd8e94..2d3dc20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,26 @@ on: pull_request: branches: [main] jobs: - check: + registry: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Repository sanity check - run: test -f README.md + - uses: pnpm/action-setup@v4 + with: + version: 11 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Typecheck + run: pnpm typecheck + - name: Lint + run: pnpm lint + - name: Test + run: pnpm test + - name: Build registry + run: pnpm build + - name: Built registry output must be committed and in sync + run: git diff --exit-code r/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c45938 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.log +.DS_Store diff --git a/README.md b/README.md index f73f703..d6ab963 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,51 @@ Full, copy-paste-ready snippets for every stack live in the docs: **[docs.logo.d --- +## React components (shadcn/ui) + +For React apps, this repo is also the official Logo.dev [shadcn/ui](https://ui.shadcn.com) registry. It ships production-grade components for the logo patterns most apps end up rebuilding by hand: image fallbacks, dark mode, retina, and debounced company search. + +Install a component with the shadcn CLI: + +```bash +npx shadcn@latest add https://www.logo.dev/r/logo.json +``` + +You can also install straight from this repo, pinned to a branch, tag, or commit: + +```bash +npx shadcn@latest add logo-dev/logo-api/logo +``` + +| Component | What you get | +| --- | --- | +| `logo` | A logo that never breaks: domain/name/ticker/crypto/ISIN lookup, retina srcSet, automatic dark-mode variants, monogram/initials/custom fallbacks | +| `logo-avatar` | A logo in a shadcn Avatar shell with initials fallback — CRM rows, transaction feeds | +| `brand-search` | Company autocomplete combobox backed by the Search API, with a Next.js route that keeps your secret key server-side | +| `logo-wall` | Customer/integration logo grid from a list of domains, grayscale-to-color hover | +| `attribution` | The attribution link free plans require in production | +| `logo-lib` | The typed URL builder underneath all of the above, useful on its own | + +Components read your publishable key from `NEXT_PUBLIC_LOGO_DEV_TOKEN`; `brand-search` also needs `LOGO_DEV_SECRET_KEY` on the server. Both are added to `.env.local` on install. Full guide: **[shadcn/ui components →](https://docs.logo.dev/integrations/shadcn)** + +
+Developing the registry + +
+ +```bash +pnpm install +pnpm test # URL builder unit tests +pnpm typecheck +pnpm build # shadcn build → r/*.json (committed; CI checks it's in sync) +``` + +Component sources live in `registry/new-york/`. `components/ui/` holds vendored shadcn primitives used only for typechecking — consumers get those from ui.shadcn.com. + +
+ +--- + ## Migrating from Clearbit **Clearbit's Logo API shut down on December 8, 2025.** If your app still points at `logo.clearbit.com`, the logos are broken. diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..68cc716 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,43 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "extends": ["ultracite/biome/core", "ultracite/biome/react"], + "files": { + // `r/` is generated by `shadcn build` (pnpm build); never lint output. + // `assets/` holds static brand SVGs, not source to format. + "includes": ["!!r", "!!assets"] + }, + "linter": { + "rules": { + "style": { + "useFilenamingConvention": "off", + "useNamingConvention": "off", + "noMagicNumbers": "off" + } + } + }, + "overrides": [ + { + // Vendored verbatim from shadcn/ui for typechecking the registry + // sources. Never distributed by this registry (consumers pull these + // primitives from ui.shadcn.com), so they aren't held to our rules. + "includes": ["components/ui/**"], + "linter": { + "enabled": false + } + }, + { + // Registry sources ship verbatim into consumer projects. Raw is + // deliberate (img.logo.dev is already an image CDN; next/image would + // couple consumers to Next.js), and repo-level overrides keep inline + // suppression comments out of the code customers receive. + "includes": ["registry/**"], + "linter": { + "rules": { + "performance": { + "noImgElement": "off" + } + } + } + } + ] +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..9d1be47 --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "css": "styles/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..8679f1d --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client"; + +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarFallback, AvatarImage }; diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..8e9bf8a --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,57 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + defaultVariants: { + size: "default", + variant: "default", + }, + variants: { + size: { + default: "h-9 px-4 py-2", + icon: "h-9 w-9", + lg: "h-10 rounded-md px-8", + sm: "h-8 rounded-md px-3 text-xs", + }, + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + }, + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/components/ui/command.tsx b/components/ui/command.tsx new file mode 100644 index 0000000..87e2593 --- /dev/null +++ b/components/ui/command.tsx @@ -0,0 +1,148 @@ +"use client"; + +import type { DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; +import * as React from "react"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +const CommandDialog = ({ children, ...props }: DialogProps) => ( + + + + {children} + + + +); + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +); +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +}; diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..7b633d5 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx new file mode 100644 index 0000000..c4730e7 --- /dev/null +++ b/components/ui/popover.tsx @@ -0,0 +1,33 @@ +"use client"; + +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }; diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..365058c --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e6ad18e --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "logodev-registry", + "private": true, + "type": "module", + "scripts": { + "build": "shadcn build --output r", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "lint": "biome check .", + "fix": "biome check --write ." + }, + "devDependencies": { + "@biomejs/biome": "^2.5.2", + "@radix-ui/react-avatar": "^1.2.1", + "@radix-ui/react-dialog": "^1.1.18", + "@radix-ui/react-popover": "^1.1.18", + "@radix-ui/react-slot": "^1.3.0", + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^1.23.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "shadcn": "^4.13.0", + "tailwind-merge": "^3.6.0", + "typescript": "^6.0.3", + "ultracite": "^7.9.0", + "vitest": "^4.1.9" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d3228a0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4702 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@biomejs/biome': + specifier: ^2.5.2 + version: 2.5.2 + '@radix-ui/react-avatar': + specifier: ^1.2.1 + version: 1.2.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': + specifier: ^1.1.18 + version: 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-popover': + specifier: ^1.1.18 + version: 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': + specifier: ^1.3.0 + version: 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@types/node': + specifier: ^24.13.2 + version: 24.13.2 + '@types/react': + specifier: ^19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + lucide-react: + specifier: ^1.23.0 + version: 1.23.0(react@19.2.7) + react: + specifier: ^19.2.7 + version: 19.2.7 + react-dom: + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) + shadcn: + specifier: ^4.13.0 + version: 4.13.0(typescript@6.0.3) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + ultracite: + specifier: ^7.9.0 + version: 7.9.0(eslint@10.6.0)(typescript@6.0.3) + vitest: + specifier: ^4.1.9 + version: 4.1.9(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(yaml@2.9.0)) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.5.2': + resolution: {integrity: sha512-VQ3RCqr7JmDIX+w6stWYl+g/3bYofN3q2wDBHUKKc/c7i5QWrFKFBZYCYPWTE6agsUPMIZZe6/CMmVUfUAhkKA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.5.2': + resolution: {integrity: sha512-e7P3P7EkwFc/KiX2AHw4YDLIBOMfG9CPCAwy52k5Bp0dfhkozx9hf6wCmIr2QeXy2XeccJ3V/Sg+hDmzYEqxSg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.5.2': + resolution: {integrity: sha512-ymzMvjC1Jg0b9K0D26ZdARqFQXs7MocfLC5FOCGfkC0Ss+ACUJkX5364ZM5nT4NLZanHRZNVrZEy+Ibwcvux/g==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.5.2': + resolution: {integrity: sha512-w+ANG0ZvTu9IeEg9QnstoOnk6L0fpwJifW6aHR18+cb5Z39bkANItYjAfMrnvce5tmMK+IQ6nPX7/kQFdam5iw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.5.2': + resolution: {integrity: sha512-t7sseOmqND57uUWTwlawU6BYj+J06T/9EkydzBhkrgw/FK3QVhjU2wsJR0frljrKZ0/I8A/rYw7284QgqjQfIQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.5.2': + resolution: {integrity: sha512-VArNLAzND063tF+XY0yPyM+DyahpzOMzOAvb7qs259nhjJWRjvjZdssuA+Rfl+l07+NOesKZ0Xu2yFrXyBMtzw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.5.2': + resolution: {integrity: sha512-M/lOZrewzTCRDINbjhQ1gYYru37KlD3kJBQwwKCG0ckz5E9IZwIoJ3X0wBwRXA+yBDIwWUuPBHS67HzJY4dTfA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.5.2': + resolution: {integrity: sha512-kbjFFKyZlzYnAuw7sRy5qDoFG6zrP40UK08oPQsWK0ct3NMnGSt+Bs1iviEEyEIP57N5MrykGXdO/wRiaR4lww==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.5.2': + resolution: {integrity: sha512-4InchVpdVmdkkkgjQqKpgvyu+VPnoF/7RPSw5YATgEVpt2j72wcCAeV5TwaE9ZGJUZWZn7v2CwSAj6CrMJEx8A==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} + engines: {node: '>= 20.12.0'} + + '@dotenvx/dotenvx@1.75.1': + resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==} + hasBin: true + + '@dotenvx/primitives@0.8.0': + resolution: {integrity: sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/types@0.138.0': + resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} + + '@radix-ui/primitive@1.1.4': + resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} + + '@radix-ui/react-arrow@1.1.11': + resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.2.1': + resolution: {integrity: sha512-+8PWoLLZv3AVb5m0pvoiOca/bQGzc9vPVb+982HB2x3Un0DpYEPM3zLMl4oqRpBsocJuNqLkiv/HXTnTrlwr4g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.4': + resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.18': + resolution: {integrity: sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.14': + resolution: {integrity: sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.11': + resolution: {integrity: sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popover@1.1.18': + resolution: {integrity: sha512-qdXDes+eHlnMUGlBAAAe5EG7oOQvqsXuq4mq585diMudg80iB+jHbsSeG3+Q4eWNsogNyhqU2p/3i+Y0iEepqg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.3.2': + resolution: {integrity: sha512-3QXNeMkdshed1MR3LNoiCirBywRFPkD8ETJa/HlPuLwSajaQixf2ro+isoDNJlGABg9ug41XuZpINZJIle4XWg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.6': + resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.2': + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.2': + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/rect@1.1.2': + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} + + '@rolldown/binding-android-arm64@1.1.4': + resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.4': + resolution: {integrity: sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.4': + resolution: {integrity: sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.4': + resolution: {integrity: sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.4': + resolution: {integrity: sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.4': + resolution: {integrity: sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.4': + resolution: {integrity: sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.4': + resolution: {integrity: sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.4': + resolution: {integrity: sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.4': + resolution: {integrity: sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.4': + resolution: {integrity: sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.4': + resolution: {integrity: sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.4': + resolution: {integrity: sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.4': + resolution: {integrity: sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.4': + resolution: {integrity: sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@ts-morph/common@0.27.0': + resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/validate-npm-package-name@4.0.2': + resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} + + '@typescript-eslint/project-service@8.62.1': + resolution: {integrity: sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.62.1': + resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.62.1': + resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.62.1': + resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.62.1': + resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.62.1': + resolution: {integrity: sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.62.1': + resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + atomically@1.7.0: + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} + engines: {node: '>=10.12.0'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.42: + resolution: {integrity: sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==} + engines: {node: '>=6.0.0'} + hasBin: true + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.4: + resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001800: + resolution: {integrity: sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + citty@0.2.2: + resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + + conf@10.2.0: + resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} + engines: {node: '>=12'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debounce-fn@4.0.0: + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} + engines: {node: '>=10'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.387: + resolution: {integrity: sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.0: + resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.6.0: + resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.3: + resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@11.3.6: + resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + fuzzysort@3.1.0: + resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-own-enumerable-keys@1.0.0: + resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} + engines: {node: '>=14.16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@7.0.3: + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.23.0: + resolution: {integrity: sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@3.1.0: + resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} + engines: {node: '>=8'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + engines: {node: '>=18'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nypm@0.6.8: + resolution: {integrity: sha512-Q9K4Diu6l5u6xJQogeFSs/zKtyMSgFKFtRQV+tHP4kL7KPm2grpBU0dFIwFaXwNxN0MtfKWc43VpCugAa+LPsw==} + engines: {node: '>=18'} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + postcss-selector-parser@7.1.4: + resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + engines: {node: '>=4'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + recast@0.23.12: + resolution: {integrity: sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==} + engines: {node: '>= 4'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.1.4: + resolution: {integrity: sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shadcn@4.13.0: + resolution: {integrity: sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==} + engines: {node: '>=20.18.1'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-object@5.0.0: + resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} + engines: {node: '>=14.16'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + systeminformation@5.31.13: + resolution: {integrity: sha512-iUJXJoKzm4vtLSeT3nwe2s9QjoJAxHg7wYJ0KaQ54Xy2u9jsTq0ULWQQ0+T72FXjX2XnGqubazNx9lUfng7ELw==} + engines: {node: '>=8.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] + hasBin: true + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-morph@26.0.0: + resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ultracite@7.9.0: + resolution: {integrity: sha512-Lkh6MXuX/vI89jeDIaEiOifI1BVXEJmk6e+igKRGAzdp5jKzeFa5d5A0tpJmHGCxXzd42jVwNpcs7jqm87IfNg==} + hasBin: true + peerDependencies: + oxfmt: '>=0.1.0' + oxlint: ^1.0.0 + peerDependenciesMeta: + oxfmt: + optional: true + oxlint: + optional: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@8.1.3: + resolution: {integrity: sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-spinner@1.2.0: + resolution: {integrity: sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@biomejs/biome@2.5.2': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.5.2 + '@biomejs/cli-darwin-x64': 2.5.2 + '@biomejs/cli-linux-arm64': 2.5.2 + '@biomejs/cli-linux-arm64-musl': 2.5.2 + '@biomejs/cli-linux-x64': 2.5.2 + '@biomejs/cli-linux-x64-musl': 2.5.2 + '@biomejs/cli-win32-arm64': 2.5.2 + '@biomejs/cli-win32-x64': 2.5.2 + + '@biomejs/cli-darwin-arm64@2.5.2': + optional: true + + '@biomejs/cli-darwin-x64@2.5.2': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.5.2': + optional: true + + '@biomejs/cli-linux-arm64@2.5.2': + optional: true + + '@biomejs/cli-linux-x64-musl@2.5.2': + optional: true + + '@biomejs/cli-linux-x64@2.5.2': + optional: true + + '@biomejs/cli-win32-arm64@2.5.2': + optional: true + + '@biomejs/cli-win32-x64@2.5.2': + optional: true + + '@clack/core@1.4.3': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.7.0': + dependencies: + '@clack/core': 1.4.3 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@dotenvx/dotenvx@1.75.1': + dependencies: + '@dotenvx/primitives': 0.8.0 + commander: 11.1.0 + conf: 10.2.0 + dotenv: 17.4.2 + enquirer: 2.4.1 + env-paths: 2.2.1 + execa: 5.1.1 + fdir: 6.5.0(picomatch@4.0.5) + ignore: 5.3.2 + object-treeify: 1.1.33 + open: 8.4.2 + picomatch: 4.0.5 + systeminformation: 5.31.13 + undici: 7.28.0 + which: 4.0.0 + yocto-spinner: 1.2.0 + + '@dotenvx/primitives@0.8.0': {} + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0)': + dependencies: + eslint: 10.6.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@floating-ui/utils@0.2.11': {} + + '@hono/node-server@1.19.14(hono@4.12.27)': + dependencies: + hono: 4.12.27 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.27) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.27 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-project/types@0.138.0': {} + + '@radix-ui/primitive@1.1.4': {} + + '@radix-ui/react-arrow@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-avatar@1.2.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-context@1.1.4(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-dialog@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-dismissable-layer@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-focus-scope@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-popover@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-popper@1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/rect': 1.1.2 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/rect': 1.1.2 + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/rect@1.1.2': {} + + '@rolldown/binding-android-arm64@1.1.4': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.4': + optional: true + + '@rolldown/binding-darwin-x64@1.1.4': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.4': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.4': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.4': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.4': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.4': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.4': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.4': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.4': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.4': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.4': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.4': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.4': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@ts-morph/common@0.27.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 10.2.5 + path-browserify: 1.0.1 + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.13.2': + dependencies: + undici-types: 7.18.2 + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/validate-npm-package-name@4.0.2': {} + + '@typescript-eslint/project-service@8.62.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3) + '@typescript-eslint/types': 8.62.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.62.1': + dependencies: + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/visitor-keys': 8.62.1 + + '@typescript-eslint/tsconfig-utils@8.62.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/types@8.62.1': {} + + '@typescript-eslint/typescript-estree@8.62.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.62.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@6.0.3) + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/visitor-keys': 8.62.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.62.1(eslint@10.6.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@typescript-eslint/scope-manager': 8.62.1 + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@6.0.3) + eslint: 10.6.0 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.62.1': + dependencies: + '@typescript-eslint/types': 8.62.1 + eslint-visitor-keys: 5.0.1 + + '@vitest/expect@4.1.9': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.9(vite@8.1.3(@types/node@24.13.2)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.3(@types/node@24.13.2)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.9': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.9': + dependencies: + '@vitest/utils': 4.1.9 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.9': {} + + '@vitest/utils@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + ajv-formats@2.1.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + assertion-error@2.0.1: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + atomically@1.7.0: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.42: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@5.0.7: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.4: + dependencies: + baseline-browser-mapping: 2.10.42 + caniuse-lite: 1.0.30001800 + electron-to-chromium: 1.5.387 + node-releases: 2.0.50 + update-browserslist-db: 1.2.3(browserslist@4.28.4) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001800: {} + + chai@6.2.2: {} + + chalk@5.6.2: {} + + citty@0.2.2: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dialog': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + code-block-writer@13.0.3: {} + + commander@11.1.0: {} + + commander@14.0.3: {} + + commander@15.0.0: {} + + conf@10.2.0: + dependencies: + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + atomically: 1.7.0 + debounce-fn: 4.0.0 + dot-prop: 6.0.1 + env-paths: 2.2.1 + json-schema-typed: 7.0.3 + onetime: 5.1.2 + pkg-up: 3.1.0 + semver: 7.8.5 + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.2(typescript@6.0.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 6.0.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debounce-fn@4.0.0: + dependencies: + mimic-fn: 3.1.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@1.7.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@2.0.0: {} + + define-lazy-prop@3.0.0: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + diff@8.0.4: {} + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.387: {} + + emoji-regex@10.6.0: {} + + encodeurl@2.0.0: {} + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.6.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 5.0.1 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + expect-type@1.4.0: {} + + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.3: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@11.3.6: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + fuzzysort@3.1.0: {} + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.6.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-own-enumerable-keys@1.0.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@6.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.12.27: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + human-signals@8.0.1: {} + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-docker@2.2.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-regexp@3.1.0: {} + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + jose@6.2.3: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@7.0.3: {} + + json-schema-typed@8.0.2: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.23.0(react@19.2.7): + dependencies: + react: 19.2.7 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-fn@2.1.0: {} + + mimic-fn@3.1.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.7 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + node-releases@2.0.50: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nypm@0.6.8: + dependencies: + citty: 0.2.2 + pathe: 2.0.3 + tinyexec: 1.2.4 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-treeify@1.1.33: {} + + obug@2.1.3: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@4.0.0: {} + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.5: {} + + pkce-challenge@5.0.1: {} + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + postcss-selector-parser@7.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + prelude-ls@1.2.1: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + + react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + get-nonce: 1.0.1 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react@19.2.7: {} + + recast@0.23.12: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rolldown@1.1.4: + dependencies: + '@oxc-project/types': 0.138.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.4 + '@rolldown/binding-darwin-arm64': 1.1.4 + '@rolldown/binding-darwin-x64': 1.1.4 + '@rolldown/binding-freebsd-x64': 1.1.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.4 + '@rolldown/binding-linux-arm64-gnu': 1.1.4 + '@rolldown/binding-linux-arm64-musl': 1.1.4 + '@rolldown/binding-linux-ppc64-gnu': 1.1.4 + '@rolldown/binding-linux-s390x-gnu': 1.1.4 + '@rolldown/binding-linux-x64-gnu': 1.1.4 + '@rolldown/binding-linux-x64-musl': 1.1.4 + '@rolldown/binding-openharmony-arm64': 1.1.4 + '@rolldown/binding-wasm32-wasi': 1.1.4 + '@rolldown/binding-win32-arm64-msvc': 1.1.4 + '@rolldown/binding-win32-x64-msvc': 1.1.4 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shadcn@4.13.0(typescript@6.0.3): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@dotenvx/dotenvx': 1.75.1 + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@types/validate-npm-package-name': 4.0.2 + browserslist: 4.28.4 + commander: 14.0.3 + cosmiconfig: 9.0.2(typescript@6.0.3) + dedent: 1.7.2 + deepmerge: 4.3.1 + diff: 8.0.4 + execa: 9.6.1 + fast-glob: 3.3.3 + fs-extra: 11.3.6 + fuzzysort: 3.1.0 + kleur: 4.1.5 + open: 11.0.0 + ora: 8.2.0 + postcss: 8.5.16 + postcss-selector-parser: 7.1.4 + prompts: 2.4.2 + recast: 0.23.12 + stringify-object: 5.0.0 + tailwind-merge: 3.6.0 + ts-morph: 26.0.0 + tsconfig-paths: 4.2.0 + undici: 7.28.0 + validate-npm-package-name: 7.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - '@cfworker/json-schema' + - babel-plugin-macros + - supports-color + - typescript + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@4.1.0: {} + + stdin-discarder@0.2.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + stringify-object@5.0.0: + dependencies: + get-own-enumerable-keys: 1.0.0 + is-obj: 3.0.0 + is-regexp: 3.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@4.0.0: {} + + systeminformation@5.31.13: {} + + tailwind-merge@3.6.0: {} + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + ts-morph@26.0.0: + dependencies: + '@ts-morph/common': 0.27.0 + code-block-writer: 13.0.3 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@6.0.3: {} + + ultracite@7.9.0(eslint@10.6.0)(typescript@6.0.3): + dependencies: + '@clack/prompts': 1.7.0 + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0)(typescript@6.0.3) + commander: 15.0.0 + cross-spawn: 7.0.6 + deepmerge: 4.3.1 + glob: 13.0.6 + jsonc-parser: 3.3.1 + nypm: 0.6.8 + yaml: 2.9.0 + zod: 4.4.3 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + undici-types@7.18.2: {} + + undici@7.28.0: {} + + unicorn-magic@0.3.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.4): + dependencies: + browserslist: 4.28.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + util-deprecate@1.0.2: {} + + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + + vite@8.1.3(@types/node@24.13.2)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.16 + rolldown: 1.1.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + yaml: 2.9.0 + + vitest@4.1.9(@types/node@24.13.2)(vite@8.1.3(@types/node@24.13.2)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + es-module-lexer: 2.3.0 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.3(@types/node@24.13.2)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.2 + transitivePeerDependencies: + - msw + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.5 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yocto-queue@0.1.0: {} + + yocto-spinner@1.2.0: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + + zod@4.4.3: {} diff --git a/r/attribution.json b/r/attribution.json new file mode 100644 index 0000000..4ff49c5 --- /dev/null +++ b/r/attribution.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "attribution", + "title": "Logo.dev Attribution", + "author": "Logo.dev ", + "description": "The attribution link Logo.dev free plans require in production. Keeps the referrer intact so attribution can be verified.", + "registryDependencies": [ + "utils" + ], + "files": [ + { + "path": "registry/new-york/ui/attribution.tsx", + "content": "import type { ComponentProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface AttributionProps\n extends Omit, \"href\" | \"children\"> {\n label?: string;\n}\n\n/**\n * Attribution link required on Logo.dev free plans in production.\n * See https://docs.logo.dev/platform/attribution\n *\n * Uses rel=\"noopener\" without \"noreferrer\" on purpose: the referrer is how\n * attribution is verified.\n */\nfunction Attribution({\n label = \"Logos provided by Logo.dev\",\n className,\n ...props\n}: AttributionProps) {\n return (\n \n {label}\n \n );\n}\n\nexport type { AttributionProps };\nexport { Attribution };\n", + "type": "registry:ui" + } + ], + "categories": [ + "logos", + "branding" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/r/brand-search.json b/r/brand-search.json new file mode 100644 index 0000000..def669d --- /dev/null +++ b/r/brand-search.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "brand-search", + "title": "Brand Search", + "author": "Logo.dev ", + "description": "Company autocomplete backed by the Logo.dev Search API: a debounced combobox with logos per result, plus a Next.js route that keeps your secret key on the server.", + "registryDependencies": [ + "button", + "command", + "popover", + "https://www.logo.dev/r/logo-lib.json" + ], + "files": [ + { + "path": "registry/new-york/blocks/brand-search/brand-search.tsx", + "content": "\"use client\";\n\nimport { ChevronsUpDownIcon } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Command,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { useBrandSearch } from \"@/registry/new-york/hooks/use-brand-search\";\nimport {\n type BrandSearchResult,\n withLogoParams,\n} from \"@/registry/new-york/lib/logo-dev\";\n\ninterface BrandSearchProps {\n className?: string;\n defaultValue?: BrandSearchResult | null;\n disabled?: boolean;\n /** Server endpoint proxying the Logo.dev Search API. */\n endpoint?: string;\n /** Maximum results shown. Default 8. */\n limit?: number;\n onSelect?: (brand: BrandSearchResult) => void;\n placeholder?: string;\n /** Controlled selection. Leave undefined for uncontrolled use. */\n value?: BrandSearchResult | null;\n}\n\nconst RESULT_LOGO_PX = 20;\n\nconst resultLogoUrl = (brand: BrandSearchResult) =>\n // logo_url already carries a token; just request a crisp, small render.\n withLogoParams(brand.logo_url, {\n format: \"webp\",\n size: RESULT_LOGO_PX * 2,\n });\n\nconst BrandSearchResultItem = ({\n brand,\n onSelect,\n}: {\n brand: BrandSearchResult;\n onSelect: (brand: BrandSearchResult) => void;\n}) => {\n const handleSelect = useCallback(() => {\n onSelect(brand);\n }, [brand, onSelect]);\n return (\n \n \n {brand.name}\n \n {brand.domain}\n \n \n );\n};\n\n/**\n * Company autocomplete backed by the Logo.dev Search API — pick a company by\n * name and get its domain and logo. Requires the API route installed with\n * this block (your sk_ key stays on the server).\n */\nfunction BrandSearch({\n value,\n defaultValue,\n onSelect,\n endpoint,\n placeholder = \"Search for a company…\",\n limit,\n disabled,\n className,\n}: BrandSearchProps) {\n const [open, setOpen] = useState(false);\n const [internal, setInternal] = useState(\n defaultValue ?? null\n );\n const selected = value === undefined ? internal : value;\n\n const { query, setQuery, results, isLoading, error } = useBrandSearch({\n enabled: open,\n endpoint,\n limit,\n });\n\n const handleSelect = useCallback(\n (brand: BrandSearchResult) => {\n if (value === undefined) {\n setInternal(brand);\n }\n onSelect?.(brand);\n setOpen(false);\n },\n [value, onSelect]\n );\n\n const showStatus = results.length === 0;\n let status: string | null = null;\n if (showStatus && isLoading) {\n status = \"Searching…\";\n } else if (showStatus && error) {\n status = \"Something went wrong. Try again.\";\n } else if (showStatus && query.trim()) {\n status = \"No companies found.\";\n }\n\n return (\n \n \n \n {selected ? (\n \n \n {selected.name}\n \n ) : (\n {placeholder}\n )}\n \n \n \n \n {/* The API filters server-side; local cmdk filtering must stay off. */}\n \n \n \n {status ? (\n

\n {status}\n

\n ) : (\n \n {results.map((brand) => (\n \n ))}\n \n )}\n
\n
\n \n
\n );\n}\n\nexport type { BrandSearchProps };\nexport { BrandSearch };\n", + "type": "registry:component" + }, + { + "path": "registry/new-york/hooks/use-brand-search.ts", + "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { BrandSearchResult } from \"@/registry/new-york/lib/logo-dev\";\n\ninterface UseBrandSearchOptions {\n debounceMs?: number;\n enabled?: boolean;\n /**\n * Server endpoint that proxies the Logo.dev Search API with your secret\n * key. Defaults to the Next.js route installed with the brand-search\n * block; point it at your own proxy on other stacks.\n */\n endpoint?: string;\n /** Maximum results to keep (the API returns up to 10). Default 8. */\n limit?: number;\n}\n\nconst DEFAULT_DEBOUNCE_MS = 200;\nconst DEFAULT_LIMIT = 8;\n\nconst performSearch = async (\n endpoint: string,\n query: string,\n limit: number,\n signal: AbortSignal\n): Promise => {\n const response = await fetch(`${endpoint}?q=${encodeURIComponent(query)}`, {\n signal,\n });\n if (!response.ok) {\n throw new Error(`Brand search failed (${response.status})`);\n }\n const data = (await response.json()) as BrandSearchResult[];\n return data.slice(0, limit);\n};\n\nconst isAbortError = (caught: unknown): boolean =>\n caught instanceof DOMException && caught.name === \"AbortError\";\n\n/**\n * Debounced company search against the Logo.dev Search API. Stale in-flight\n * requests are aborted, and previous results stay visible while a new query\n * loads so the list doesn't flicker.\n */\nfunction useBrandSearch({\n endpoint = \"/api/logo-dev/search\",\n debounceMs = DEFAULT_DEBOUNCE_MS,\n limit = DEFAULT_LIMIT,\n enabled = true,\n}: UseBrandSearchOptions = {}) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState(null);\n const abortRef = useRef(null);\n\n useEffect(() => {\n const trimmed = query.trim();\n if (!(enabled && trimmed)) {\n abortRef.current?.abort();\n setResults([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const timer = setTimeout(() => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n setIsLoading(true);\n\n performSearch(endpoint, trimmed, limit, controller.signal)\n .then((data) => {\n setResults(data);\n setError(null);\n })\n .catch((caught: unknown) => {\n if (!isAbortError(caught)) {\n setError(\n caught instanceof Error\n ? caught\n : new Error(\"Brand search failed\")\n );\n }\n })\n .finally(() => {\n // A newer request may already own the loading state.\n if (abortRef.current === controller) {\n setIsLoading(false);\n }\n });\n }, debounceMs);\n\n return () => clearTimeout(timer);\n }, [query, enabled, endpoint, debounceMs, limit]);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n return { error, isLoading, query, results, setQuery };\n}\n\nexport type { UseBrandSearchOptions };\nexport { useBrandSearch };\n", + "type": "registry:hook" + }, + { + "path": "registry/new-york/blocks/brand-search/route.ts", + "content": "/**\n * Proxy for the Logo.dev Search API. Keeps your secret (sk_) key on the\n * server — never call api.logo.dev directly from the browser.\n *\n * NOTE: This route is unauthenticated. Add your own rate limiting and/or\n * auth before shipping it on a public site.\n */\n\nconst SEARCH_API_URL = \"https://api.logo.dev/search\";\nconst MAX_QUERY_LENGTH = 100;\nconst HTTP_BAD_REQUEST = 400;\nconst HTTP_NOT_FOUND = 404;\nconst HTTP_SERVER_ERROR = 500;\nconst HTTP_BAD_GATEWAY = 502;\n\nexport async function GET(request: Request): Promise {\n const query = new URL(request.url).searchParams.get(\"q\")?.trim() ?? \"\";\n if (!query) {\n return Response.json([]);\n }\n if (query.length > MAX_QUERY_LENGTH) {\n return Response.json(\n { error: \"Query too long\" },\n { status: HTTP_BAD_REQUEST }\n );\n }\n\n const key = process.env.LOGO_DEV_SECRET_KEY;\n if (!key || key.startsWith(\"pk_\")) {\n return Response.json(\n {\n error:\n \"Set LOGO_DEV_SECRET_KEY to a secret (sk_) key. Get one at https://www.logo.dev/dashboard\",\n },\n { status: HTTP_SERVER_ERROR }\n );\n }\n\n let upstream: Response;\n try {\n upstream = await fetch(`${SEARCH_API_URL}?q=${encodeURIComponent(query)}`, {\n headers: { Authorization: `Bearer ${key}` },\n });\n } catch {\n return Response.json([], { status: HTTP_BAD_GATEWAY });\n }\n\n // The Search API returns 404 when nothing matches — that's an empty\n // result, not an error.\n if (upstream.status === HTTP_NOT_FOUND) {\n return Response.json([]);\n }\n if (!upstream.ok) {\n return Response.json([], { status: HTTP_BAD_GATEWAY });\n }\n\n const results: unknown = await upstream.json();\n return Response.json(results, {\n headers: {\n \"Cache-Control\": \"private, max-age=60, stale-while-revalidate=300\",\n },\n });\n}\n", + "type": "registry:file", + "target": "app/api/logo-dev/search/route.ts" + } + ], + "envVars": { + "LOGO_DEV_SECRET_KEY": "" + }, + "docs": "Set LOGO_DEV_SECRET_KEY (sk_) in .env.local — server-only, never expose it to the browser. Get keys at https://www.logo.dev/dashboard. Not on Next.js? Re-create the proxy from app/api/logo-dev/search/route.ts in your backend and pass `endpoint` to useBrandSearch.", + "categories": [ + "logos", + "search", + "forms" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/r/logo-avatar.json b/r/logo-avatar.json new file mode 100644 index 0000000..9a7bf63 --- /dev/null +++ b/r/logo-avatar.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "logo-avatar", + "title": "Logo Avatar", + "author": "Logo.dev ", + "description": "A company logo in a shadcn Avatar shell with an initials fallback — for CRM account lists, transaction feeds, and directory rows.", + "registryDependencies": [ + "avatar", + "https://www.logo.dev/r/logo.json" + ], + "files": [ + { + "path": "registry/new-york/ui/logo-avatar.tsx", + "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Avatar } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport { Logo, type LogoSourceProps } from \"@/registry/new-york/ui/logo\";\n\ntype LogoAvatarProps = LogoSourceProps &\n Omit, \"children\"> & {\n /** Avatar dimension in pixels. Default 32. */\n size?: number;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\n/**\n * A company logo in a shadcn Avatar shell with an initials fallback — for\n * CRM rows, transaction feeds, and anywhere you'd show a user avatar.\n *\n * Defaults to rounded-md: full circles crop most logos. Pass\n * className=\"rounded-full\" if you want circular avatars anyway.\n */\nfunction LogoAvatar({\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n label,\n theme,\n greyscale,\n token,\n className,\n style,\n ...avatarProps\n}: LogoAvatarProps) {\n const sourceProps = {\n crypto,\n domain,\n isin,\n name,\n ticker,\n } as LogoSourceProps;\n\n return (\n \n \n \n );\n}\n\nexport type { LogoAvatarProps };\nexport { LogoAvatar };\n", + "type": "registry:ui" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "categories": [ + "logos", + "branding" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/r/logo-lib.json b/r/logo-lib.json new file mode 100644 index 0000000..e79bbcf --- /dev/null +++ b/r/logo-lib.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "logo-lib", + "title": "Logo.dev URL Builder", + "author": "Logo.dev ", + "description": "Typed URL builder, retina srcset helper, and token resolution for the Logo.dev Logo Image API. Supports domain, name, ticker, crypto, and ISIN lookups.", + "files": [ + { + "path": "registry/new-york/lib/logo-dev.ts", + "content": "/**\n * Logo.dev URL builder and shared types.\n *\n * Docs: https://docs.logo.dev\n * Get a free publishable (pk_) key: https://www.logo.dev/dashboard\n */\n\nconst LOGO_DEV_IMG_BASE = \"https://img.logo.dev\";\nconst MIN_SIZE = 1;\nconst MAX_SIZE = 800;\nconst DEFAULT_SIZE = 64;\n\n/**\n * One of the five Logo.dev lookup types. A plain string is treated as a\n * domain (`\"stripe.com\"`).\n */\nexport type LogoSource =\n | { domain: string }\n | { name: string }\n | { ticker: string }\n | { crypto: string }\n | { isin: string };\n\nexport type LogoKind = \"domain\" | \"name\" | \"ticker\" | \"crypto\" | \"isin\";\n\nexport interface LogoUrlOptions {\n /**\n * What the API returns when no logo exists: `monogram` (default) serves a\n * generated letter tile with HTTP 200; `404` returns an HTTP 404 so an\n * `onError` handler can render a local fallback instead.\n */\n fallback?: \"monogram\" | \"404\";\n /** Image format. `svg` requires an Enterprise plan — never used by default. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /** Return a black-and-white version of the logo. */\n greyscale?: boolean;\n /** Render at 2× the requested size for high-density displays. */\n retina?: boolean;\n /** Logical pixel size (width and height). Clamped to 1–800. Default 64. */\n size?: number;\n /**\n * `light` inverts dark logos for light backgrounds, `dark` inverts light\n * logos for dark backgrounds. `auto` (default) requests the logo as-is.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n}\n\n/** A result row from the Logo.dev Brand Search API. */\nexport interface BrandSearchResult {\n domain: string;\n logo_url: string;\n name: string;\n}\n\nlet warnedMissingToken = false;\n\nconst envToken = (): string | undefined => {\n if (typeof process === \"undefined\") {\n // Non-Node bundlers (e.g. Vite) don't define `process`; pass `token` instead.\n return;\n }\n return process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN;\n};\n\n/**\n * Resolve the publishable API token: explicit argument first, then the\n * NEXT_PUBLIC_LOGO_DEV_TOKEN environment variable.\n */\nexport const getLogoDevToken = (token?: string): string => {\n const resolved = token ?? envToken() ?? \"\";\n if (!(resolved || warnedMissingToken) && typeof console !== \"undefined\") {\n warnedMissingToken = true;\n console.warn(\n \"[logo.dev] No API token found. Set NEXT_PUBLIC_LOGO_DEV_TOKEN or pass `token`. Get a free key at https://www.logo.dev/dashboard\"\n );\n }\n return resolved;\n};\n\n/** Normalize a LogoSource (or bare domain string) into its kind and value. */\nexport const logoSourceValue = (\n source: LogoSource | string\n): { kind: LogoKind; value: string } => {\n if (typeof source === \"string\") {\n return { kind: \"domain\", value: source };\n }\n if (\"domain\" in source) {\n return { kind: \"domain\", value: source.domain };\n }\n if (\"name\" in source) {\n return { kind: \"name\", value: source.name };\n }\n if (\"ticker\" in source) {\n return { kind: \"ticker\", value: source.ticker };\n }\n if (\"crypto\" in source) {\n return { kind: \"crypto\", value: source.crypto };\n }\n return { kind: \"isin\", value: source.isin };\n};\n\nconst buildPath = (kind: LogoKind, value: string): string => {\n const encoded = encodeURIComponent(value.trim());\n return kind === \"domain\" ? encoded : `${kind}/${encoded}`;\n};\n\nconst clampSize = (size: number): number => {\n if (!Number.isFinite(size)) {\n return DEFAULT_SIZE;\n }\n return Math.min(MAX_SIZE, Math.max(MIN_SIZE, Math.round(size)));\n};\n\n/** Build an img.logo.dev URL for any lookup type. */\nexport const logoDevUrl = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): string => {\n const { kind, value } = logoSourceValue(source);\n const {\n size = DEFAULT_SIZE,\n format = \"webp\",\n theme,\n greyscale,\n retina,\n fallback,\n token,\n } = options;\n\n const params = new URLSearchParams();\n params.set(\"token\", getLogoDevToken(token));\n params.set(\"size\", String(clampSize(size)));\n params.set(\"format\", format);\n if (theme === \"light\" || theme === \"dark\") {\n params.set(\"theme\", theme);\n }\n if (greyscale) {\n params.set(\"greyscale\", \"true\");\n }\n if (retina) {\n params.set(\"retina\", \"true\");\n }\n if (fallback === \"404\") {\n params.set(\"fallback\", \"404\");\n }\n\n return `${LOGO_DEV_IMG_BASE}/${buildPath(kind, value)}?${params.toString()}`;\n};\n\n/**\n * Build `src` and a 1x/2x `srcSet` for crisp logos on high-density displays.\n * The 2x candidate uses the API's `retina` parameter, which renders at twice\n * the requested size.\n */\nexport const logoDevSrcSet = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): { src: string; srcSet: string } => {\n const src = logoDevUrl(source, { ...options, retina: false });\n const src2x = logoDevUrl(source, { ...options, retina: true });\n return { src, srcSet: `${src} 1x, ${src2x} 2x` };\n};\n\n/**\n * Append or override image parameters on an existing img.logo.dev URL, such\n * as the `logo_url` returned by the Brand Search API (which already carries\n * a token). Returns the input unchanged if it isn't a valid URL.\n */\nexport const withLogoParams = (\n logoUrl: string,\n options: Omit = {}\n): string => {\n let url: URL;\n try {\n url = new URL(logoUrl);\n } catch {\n return logoUrl;\n }\n const { size, format, theme, greyscale, retina, fallback } = options;\n if (size !== undefined) {\n url.searchParams.set(\"size\", String(clampSize(size)));\n }\n if (format) {\n url.searchParams.set(\"format\", format);\n }\n if (theme === \"light\" || theme === \"dark\") {\n url.searchParams.set(\"theme\", theme);\n }\n if (greyscale !== undefined) {\n url.searchParams.set(\"greyscale\", greyscale ? \"true\" : \"false\");\n }\n if (retina !== undefined) {\n url.searchParams.set(\"retina\", retina ? \"true\" : \"false\");\n }\n if (fallback === \"404\") {\n url.searchParams.set(\"fallback\", \"404\");\n }\n return url.toString();\n};\n\nconst WORD_SEPARATORS = /[\\s\\-_]+/;\n\n/**\n * Initials for a local fallback tile: first letters of the first two words\n * (\"Andreessen Horowitz\" → \"AH\"), or the first character for single words\n * and domains (\"stripe.com\" → \"S\").\n */\nexport const logoInitials = (label: string): string => {\n const words = label.trim().split(WORD_SEPARATORS).filter(Boolean);\n const first = words.at(0)?.at(0);\n if (!first) {\n return \"?\";\n }\n const second = words.length > 1 ? words.at(1)?.at(0) : undefined;\n return `${first}${second ?? \"\"}`.toUpperCase();\n};\n", + "type": "registry:lib" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Get a free publishable (pk_) key at https://www.logo.dev/dashboard and set NEXT_PUBLIC_LOGO_DEV_TOKEN in .env.local.", + "categories": [ + "logos", + "branding" + ], + "type": "registry:lib" +} \ No newline at end of file diff --git a/r/logo-wall.json b/r/logo-wall.json new file mode 100644 index 0000000..04c328f --- /dev/null +++ b/r/logo-wall.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "logo-wall", + "title": "Logo Wall", + "author": "Logo.dev ", + "description": "A customer or integration logo grid built from a list of domains, with grayscale-to-color hover and built-in attribution. Server-component safe.", + "registryDependencies": [ + "https://www.logo.dev/r/logo.json", + "https://www.logo.dev/r/attribution.json" + ], + "files": [ + { + "path": "registry/new-york/blocks/logo-wall/logo-wall.tsx", + "content": "import type { ComponentProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Attribution } from \"@/registry/new-york/ui/attribution\";\nimport { Logo } from \"@/registry/new-york/ui/logo\";\n\ntype LogoWallBrand = string | { domain: string; name?: string; href?: string };\n\ninterface LogoWallProps extends ComponentProps<\"div\"> {\n /**\n * Renders the Logo.dev attribution link below the grid. Required on free\n * plans in production (https://docs.logo.dev/platform/attribution) — only\n * disable this on a paid plan.\n */\n attribution?: boolean;\n /** Domains to display, optionally with a display name and link. */\n brands: LogoWallBrand[];\n /** Grid columns from the `sm` breakpoint up (2 below it). Default 4. */\n columns?: 2 | 3 | 4 | 5 | 6;\n /** Muted grayscale logos that regain color on hover. Default true. */\n grayscale?: boolean;\n /** Logo height in pixels. Default 32. */\n size?: number;\n theme?: \"light\" | \"dark\" | \"auto\";\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n}\n\nconst COLUMN_CLASSES: Record, string> = {\n 2: \"sm:grid-cols-2\",\n 3: \"sm:grid-cols-3\",\n 4: \"sm:grid-cols-4\",\n 5: \"sm:grid-cols-5\",\n 6: \"sm:grid-cols-6\",\n};\n\nconst normalizeBrand = (brand: LogoWallBrand) =>\n typeof brand === \"string\" ? { domain: brand } : brand;\n\n/**\n * A customer or integration logo grid from a list of domains. Server-safe:\n * hover color is pure CSS, so it renders fine in React Server Components.\n */\nfunction LogoWall({\n brands,\n size = 32,\n columns = 4,\n grayscale = true,\n theme,\n attribution = true,\n token,\n className,\n ...props\n}: LogoWallProps) {\n return (\n
\n \n {brands.map(normalizeBrand).map(({ domain, name, href }) => {\n const logo = (\n \n );\n return (\n
  • \n {href ? (\n \n {logo}\n \n ) : (\n logo\n )}\n
  • \n );\n })}\n \n {attribution ? (\n
    \n \n
    \n ) : null}\n
    \n );\n}\n\nexport type { LogoWallBrand, LogoWallProps };\nexport { LogoWall };\n", + "type": "registry:component" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "categories": [ + "logos", + "branding", + "marketing" + ], + "type": "registry:component" +} \ No newline at end of file diff --git a/r/logo.json b/r/logo.json new file mode 100644 index 0000000..86960f5 --- /dev/null +++ b/r/logo.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "logo", + "title": "Logo", + "author": "Logo.dev ", + "description": "A company logo that never breaks: look up by domain, name, ticker, crypto, or ISIN with a retina srcSet, automatic dark-mode variants, and layered fallbacks (API monogram, local initials, or a custom node).", + "registryDependencies": [ + "https://www.logo.dev/r/logo-lib.json" + ], + "files": [ + { + "path": "registry/new-york/ui/logo.tsx", + "content": "\"use client\";\n\nimport {\n type ComponentProps,\n type CSSProperties,\n type ReactEventHandler,\n type ReactNode,\n useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n type LogoSource,\n type LogoUrlOptions,\n logoDevSrcSet,\n logoDevUrl,\n logoInitials,\n logoSourceValue,\n} from \"@/registry/new-york/lib/logo-dev\";\n\n/** Exactly one lookup identifier: domain, name, ticker, crypto, or isin. */\ntype LogoSourceProps =\n | {\n domain: string;\n name?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n name: string;\n domain?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n ticker: string;\n domain?: never;\n name?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n crypto: string;\n domain?: never;\n name?: never;\n ticker?: never;\n isin?: never;\n }\n | {\n isin: string;\n domain?: never;\n name?: never;\n ticker?: never;\n crypto?: never;\n };\n\ntype LogoProps = LogoSourceProps &\n Omit, \"src\" | \"srcSet\" | \"children\"> & {\n /** Logical pixel size; sets width and height so the layout never shifts. */\n size?: number;\n /** Image format. `svg` requires an Enterprise plan. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /**\n * `auto` (default) renders light and dark variants toggled by the `dark:`\n * Tailwind variant, so the right logo shows in both color schemes with\n * no theme provider. `light`/`dark` render a single variant.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Serve a 1x/2x srcSet for high-density displays. Default true. */\n retina?: boolean;\n /**\n * What renders when no logo exists: `monogram` (default) shows the\n * API-generated letter tile, `initials` shows a local initials tile, or\n * pass any React node for a custom fallback.\n */\n fallback?: \"monogram\" | \"initials\" | ReactNode;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\nconst MIN_TILE_FONT_PX = 10;\nconst TILE_FONT_RATIO = 0.4;\n\ninterface InitialsTileProps {\n className?: string;\n height: number | string;\n label: string;\n style?: CSSProperties;\n width: number | string;\n}\n\nconst InitialsTile = ({\n label,\n width,\n height,\n className,\n style,\n}: InitialsTileProps) => {\n const numericHeight = typeof height === \"number\" ? height : undefined;\n const fontSize = numericHeight\n ? Math.max(MIN_TILE_FONT_PX, Math.round(numericHeight * TILE_FONT_RATIO))\n : undefined;\n return (\n \n {logoInitials(label)}\n \n );\n};\n\nconst toLogoSource = (parts: {\n domain?: string;\n name?: string;\n ticker?: string;\n crypto?: string;\n isin?: string;\n}): LogoSource => {\n if (parts.name !== undefined) {\n return { name: parts.name };\n }\n if (parts.ticker !== undefined) {\n return { ticker: parts.ticker };\n }\n if (parts.crypto !== undefined) {\n return { crypto: parts.crypto };\n }\n if (parts.isin !== undefined) {\n return { isin: parts.isin };\n }\n return { domain: parts.domain ?? \"\" };\n};\n\n/**\n * A company logo from Logo.dev that never breaks: retina srcSet, automatic\n * dark-mode variants, and layered fallbacks (API monogram, local initials,\n * or a custom node).\n */\nfunction Logo(props: LogoProps) {\n const {\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n format,\n theme = \"auto\",\n greyscale,\n retina = true,\n fallback = \"monogram\",\n label,\n token,\n alt,\n className,\n width,\n height,\n style,\n loading = \"lazy\",\n decoding = \"async\",\n onError,\n ...imgProps\n } = props;\n\n const source = toLogoSource({ crypto, domain, isin, name, ticker });\n const { kind, value } = logoSourceValue(source);\n const labelText = label ?? name ?? value;\n\n // The error flag must reset when the logo identity changes, otherwise a\n // fallback for one company would stick around after switching to another.\n const identityKey = `${kind}:${value}:${token ?? \"\"}`;\n const [errored, setErrored] = useState(false);\n const [prevKey, setPrevKey] = useState(identityKey);\n if (prevKey !== identityKey) {\n setPrevKey(identityKey);\n setErrored(false);\n }\n\n // The API returns HTTP 200 with a generated monogram on a miss, so onError\n // only ever fires for a missing logo when the URL requests fallback=404.\n const wantsLocalFallback = fallback !== \"monogram\";\n const urlFallback: LogoUrlOptions[\"fallback\"] = wantsLocalFallback\n ? \"404\"\n : undefined;\n\n const resolvedWidth = width ?? size;\n const resolvedHeight = height ?? size;\n\n if (errored) {\n if (fallback === \"monogram\" || fallback === \"initials\") {\n return (\n \n );\n }\n return <>{fallback};\n }\n\n const sharedOptions: LogoUrlOptions = {\n fallback: urlFallback,\n format,\n greyscale,\n size,\n token,\n };\n\n const srcFor = (variant?: \"light\" | \"dark\") => {\n const options = { ...sharedOptions, theme: variant };\n if (retina) {\n return logoDevSrcSet(source, options);\n }\n return {\n src: logoDevUrl(source, { ...options, retina: false }),\n srcSet: undefined,\n };\n };\n\n const handleError: ReactEventHandler = (event) => {\n setErrored(true);\n onError?.(event);\n };\n\n const shared = {\n decoding,\n loading,\n onError: handleError,\n style,\n ...imgProps,\n };\n const resolvedAlt = alt ?? `${labelText} logo`;\n\n if (theme !== \"auto\") {\n const { src, srcSet } = srcFor(theme);\n return (\n \n );\n }\n\n const light = srcFor(\"light\");\n const dark = srcFor(\"dark\");\n return (\n <>\n \n \n \n );\n}\n\nexport type { LogoProps, LogoSourceProps };\nexport { Logo };\n", + "type": "registry:ui" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Set NEXT_PUBLIC_LOGO_DEV_TOKEN (free key: https://www.logo.dev/dashboard). Free plans require attribution in production: https://docs.logo.dev/platform/attribution", + "categories": [ + "logos", + "branding" + ], + "type": "registry:ui" +} \ No newline at end of file diff --git a/r/registry.json b/r/registry.json new file mode 100644 index 0000000..31e9430 --- /dev/null +++ b/r/registry.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "logodev", + "homepage": "https://www.logo.dev", + "items": [ + { + "name": "logo-lib", + "type": "registry:lib", + "title": "Logo.dev URL Builder", + "description": "Typed URL builder, retina srcset helper, and token resolution for the Logo.dev Logo Image API. Supports domain, name, ticker, crypto, and ISIN lookups.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/lib/logo-dev.ts", + "type": "registry:lib" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Get a free publishable (pk_) key at https://www.logo.dev/dashboard and set NEXT_PUBLIC_LOGO_DEV_TOKEN in .env.local." + }, + { + "name": "logo", + "type": "registry:ui", + "title": "Logo", + "description": "A company logo that never breaks: look up by domain, name, ticker, crypto, or ISIN with a retina srcSet, automatic dark-mode variants, and layered fallbacks (API monogram, local initials, or a custom node).", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/logo.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["https://www.logo.dev/r/logo-lib.json"], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Set NEXT_PUBLIC_LOGO_DEV_TOKEN (free key: https://www.logo.dev/dashboard). Free plans require attribution in production: https://docs.logo.dev/platform/attribution" + }, + { + "name": "logo-avatar", + "type": "registry:ui", + "title": "Logo Avatar", + "description": "A company logo in a shadcn Avatar shell with an initials fallback — for CRM account lists, transaction feeds, and directory rows.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/logo-avatar.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["avatar", "https://www.logo.dev/r/logo.json"], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + } + }, + { + "name": "attribution", + "type": "registry:ui", + "title": "Logo.dev Attribution", + "description": "The attribution link Logo.dev free plans require in production. Keeps the referrer intact so attribution can be verified.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/attribution.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["utils"] + }, + { + "name": "logo-wall", + "type": "registry:component", + "title": "Logo Wall", + "description": "A customer or integration logo grid built from a list of domains, with grayscale-to-color hover and built-in attribution. Server-component safe.", + "author": "Logo.dev ", + "categories": ["logos", "branding", "marketing"], + "files": [ + { + "path": "registry/new-york/blocks/logo-wall/logo-wall.tsx", + "type": "registry:component" + } + ], + "registryDependencies": [ + "https://www.logo.dev/r/logo.json", + "https://www.logo.dev/r/attribution.json" + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + } + }, + { + "name": "brand-search", + "type": "registry:block", + "title": "Brand Search", + "description": "Company autocomplete backed by the Logo.dev Search API: a debounced combobox with logos per result, plus a Next.js route that keeps your secret key on the server.", + "author": "Logo.dev ", + "categories": ["logos", "search", "forms"], + "files": [ + { + "path": "registry/new-york/blocks/brand-search/brand-search.tsx", + "type": "registry:component" + }, + { + "path": "registry/new-york/hooks/use-brand-search.ts", + "type": "registry:hook" + }, + { + "path": "registry/new-york/blocks/brand-search/route.ts", + "type": "registry:file", + "target": "app/api/logo-dev/search/route.ts" + } + ], + "registryDependencies": [ + "button", + "command", + "popover", + "https://www.logo.dev/r/logo-lib.json" + ], + "envVars": { + "LOGO_DEV_SECRET_KEY": "" + }, + "docs": "Set LOGO_DEV_SECRET_KEY (sk_) in .env.local — server-only, never expose it to the browser. Get keys at https://www.logo.dev/dashboard. Not on Next.js? Re-create the proxy from app/api/logo-dev/search/route.ts in your backend and pass `endpoint` to useBrandSearch." + } + ] +} diff --git a/registry.json b/registry.json new file mode 100644 index 0000000..31e9430 --- /dev/null +++ b/registry.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "logodev", + "homepage": "https://www.logo.dev", + "items": [ + { + "name": "logo-lib", + "type": "registry:lib", + "title": "Logo.dev URL Builder", + "description": "Typed URL builder, retina srcset helper, and token resolution for the Logo.dev Logo Image API. Supports domain, name, ticker, crypto, and ISIN lookups.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/lib/logo-dev.ts", + "type": "registry:lib" + } + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Get a free publishable (pk_) key at https://www.logo.dev/dashboard and set NEXT_PUBLIC_LOGO_DEV_TOKEN in .env.local." + }, + { + "name": "logo", + "type": "registry:ui", + "title": "Logo", + "description": "A company logo that never breaks: look up by domain, name, ticker, crypto, or ISIN with a retina srcSet, automatic dark-mode variants, and layered fallbacks (API monogram, local initials, or a custom node).", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/logo.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["https://www.logo.dev/r/logo-lib.json"], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + }, + "docs": "Set NEXT_PUBLIC_LOGO_DEV_TOKEN (free key: https://www.logo.dev/dashboard). Free plans require attribution in production: https://docs.logo.dev/platform/attribution" + }, + { + "name": "logo-avatar", + "type": "registry:ui", + "title": "Logo Avatar", + "description": "A company logo in a shadcn Avatar shell with an initials fallback — for CRM account lists, transaction feeds, and directory rows.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/logo-avatar.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["avatar", "https://www.logo.dev/r/logo.json"], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + } + }, + { + "name": "attribution", + "type": "registry:ui", + "title": "Logo.dev Attribution", + "description": "The attribution link Logo.dev free plans require in production. Keeps the referrer intact so attribution can be verified.", + "author": "Logo.dev ", + "categories": ["logos", "branding"], + "files": [ + { + "path": "registry/new-york/ui/attribution.tsx", + "type": "registry:ui" + } + ], + "registryDependencies": ["utils"] + }, + { + "name": "logo-wall", + "type": "registry:component", + "title": "Logo Wall", + "description": "A customer or integration logo grid built from a list of domains, with grayscale-to-color hover and built-in attribution. Server-component safe.", + "author": "Logo.dev ", + "categories": ["logos", "branding", "marketing"], + "files": [ + { + "path": "registry/new-york/blocks/logo-wall/logo-wall.tsx", + "type": "registry:component" + } + ], + "registryDependencies": [ + "https://www.logo.dev/r/logo.json", + "https://www.logo.dev/r/attribution.json" + ], + "envVars": { + "NEXT_PUBLIC_LOGO_DEV_TOKEN": "" + } + }, + { + "name": "brand-search", + "type": "registry:block", + "title": "Brand Search", + "description": "Company autocomplete backed by the Logo.dev Search API: a debounced combobox with logos per result, plus a Next.js route that keeps your secret key on the server.", + "author": "Logo.dev ", + "categories": ["logos", "search", "forms"], + "files": [ + { + "path": "registry/new-york/blocks/brand-search/brand-search.tsx", + "type": "registry:component" + }, + { + "path": "registry/new-york/hooks/use-brand-search.ts", + "type": "registry:hook" + }, + { + "path": "registry/new-york/blocks/brand-search/route.ts", + "type": "registry:file", + "target": "app/api/logo-dev/search/route.ts" + } + ], + "registryDependencies": [ + "button", + "command", + "popover", + "https://www.logo.dev/r/logo-lib.json" + ], + "envVars": { + "LOGO_DEV_SECRET_KEY": "" + }, + "docs": "Set LOGO_DEV_SECRET_KEY (sk_) in .env.local — server-only, never expose it to the browser. Get keys at https://www.logo.dev/dashboard. Not on Next.js? Re-create the proxy from app/api/logo-dev/search/route.ts in your backend and pass `endpoint` to useBrandSearch." + } + ] +} diff --git a/registry/new-york/blocks/brand-search/brand-search.tsx b/registry/new-york/blocks/brand-search/brand-search.tsx new file mode 100644 index 0000000..c1bacf9 --- /dev/null +++ b/registry/new-york/blocks/brand-search/brand-search.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { ChevronsUpDownIcon } from "lucide-react"; +import { useCallback, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { useBrandSearch } from "@/registry/new-york/hooks/use-brand-search"; +import { + type BrandSearchResult, + withLogoParams, +} from "@/registry/new-york/lib/logo-dev"; + +interface BrandSearchProps { + className?: string; + defaultValue?: BrandSearchResult | null; + disabled?: boolean; + /** Server endpoint proxying the Logo.dev Search API. */ + endpoint?: string; + /** Maximum results shown. Default 8. */ + limit?: number; + onSelect?: (brand: BrandSearchResult) => void; + placeholder?: string; + /** Controlled selection. Leave undefined for uncontrolled use. */ + value?: BrandSearchResult | null; +} + +const RESULT_LOGO_PX = 20; + +const resultLogoUrl = (brand: BrandSearchResult) => + // logo_url already carries a token; just request a crisp, small render. + withLogoParams(brand.logo_url, { + format: "webp", + size: RESULT_LOGO_PX * 2, + }); + +const BrandSearchResultItem = ({ + brand, + onSelect, +}: { + brand: BrandSearchResult; + onSelect: (brand: BrandSearchResult) => void; +}) => { + const handleSelect = useCallback(() => { + onSelect(brand); + }, [brand, onSelect]); + return ( + + + {brand.name} + + {brand.domain} + + + ); +}; + +/** + * Company autocomplete backed by the Logo.dev Search API — pick a company by + * name and get its domain and logo. Requires the API route installed with + * this block (your sk_ key stays on the server). + */ +function BrandSearch({ + value, + defaultValue, + onSelect, + endpoint, + placeholder = "Search for a company…", + limit, + disabled, + className, +}: BrandSearchProps) { + const [open, setOpen] = useState(false); + const [internal, setInternal] = useState( + defaultValue ?? null + ); + const selected = value === undefined ? internal : value; + + const { query, setQuery, results, isLoading, error } = useBrandSearch({ + enabled: open, + endpoint, + limit, + }); + + const handleSelect = useCallback( + (brand: BrandSearchResult) => { + if (value === undefined) { + setInternal(brand); + } + onSelect?.(brand); + setOpen(false); + }, + [value, onSelect] + ); + + const showStatus = results.length === 0; + let status: string | null = null; + if (showStatus && isLoading) { + status = "Searching…"; + } else if (showStatus && error) { + status = "Something went wrong. Try again."; + } else if (showStatus && query.trim()) { + status = "No companies found."; + } + + return ( + + + + + + {/* The API filters server-side; local cmdk filtering must stay off. */} + + + + {status ? ( +

    + {status} +

    + ) : ( + + {results.map((brand) => ( + + ))} + + )} +
    +
    +
    +
    + ); +} + +export type { BrandSearchProps }; +export { BrandSearch }; diff --git a/registry/new-york/blocks/brand-search/route.ts b/registry/new-york/blocks/brand-search/route.ts new file mode 100644 index 0000000..5b1ea1a --- /dev/null +++ b/registry/new-york/blocks/brand-search/route.ts @@ -0,0 +1,63 @@ +/** + * Proxy for the Logo.dev Search API. Keeps your secret (sk_) key on the + * server — never call api.logo.dev directly from the browser. + * + * NOTE: This route is unauthenticated. Add your own rate limiting and/or + * auth before shipping it on a public site. + */ + +const SEARCH_API_URL = "https://api.logo.dev/search"; +const MAX_QUERY_LENGTH = 100; +const HTTP_BAD_REQUEST = 400; +const HTTP_NOT_FOUND = 404; +const HTTP_SERVER_ERROR = 500; +const HTTP_BAD_GATEWAY = 502; + +export async function GET(request: Request): Promise { + const query = new URL(request.url).searchParams.get("q")?.trim() ?? ""; + if (!query) { + return Response.json([]); + } + if (query.length > MAX_QUERY_LENGTH) { + return Response.json( + { error: "Query too long" }, + { status: HTTP_BAD_REQUEST } + ); + } + + const key = process.env.LOGO_DEV_SECRET_KEY; + if (!key || key.startsWith("pk_")) { + return Response.json( + { + error: + "Set LOGO_DEV_SECRET_KEY to a secret (sk_) key. Get one at https://www.logo.dev/dashboard", + }, + { status: HTTP_SERVER_ERROR } + ); + } + + let upstream: Response; + try { + upstream = await fetch(`${SEARCH_API_URL}?q=${encodeURIComponent(query)}`, { + headers: { Authorization: `Bearer ${key}` }, + }); + } catch { + return Response.json([], { status: HTTP_BAD_GATEWAY }); + } + + // The Search API returns 404 when nothing matches — that's an empty + // result, not an error. + if (upstream.status === HTTP_NOT_FOUND) { + return Response.json([]); + } + if (!upstream.ok) { + return Response.json([], { status: HTTP_BAD_GATEWAY }); + } + + const results: unknown = await upstream.json(); + return Response.json(results, { + headers: { + "Cache-Control": "private, max-age=60, stale-while-revalidate=300", + }, + }); +} diff --git a/registry/new-york/blocks/logo-wall/logo-wall.tsx b/registry/new-york/blocks/logo-wall/logo-wall.tsx new file mode 100644 index 0000000..efa4885 --- /dev/null +++ b/registry/new-york/blocks/logo-wall/logo-wall.tsx @@ -0,0 +1,99 @@ +import type { ComponentProps } from "react"; +import { cn } from "@/lib/utils"; +import { Attribution } from "@/registry/new-york/ui/attribution"; +import { Logo } from "@/registry/new-york/ui/logo"; + +type LogoWallBrand = string | { domain: string; name?: string; href?: string }; + +interface LogoWallProps extends ComponentProps<"div"> { + /** + * Renders the Logo.dev attribution link below the grid. Required on free + * plans in production (https://docs.logo.dev/platform/attribution) — only + * disable this on a paid plan. + */ + attribution?: boolean; + /** Domains to display, optionally with a display name and link. */ + brands: LogoWallBrand[]; + /** Grid columns from the `sm` breakpoint up (2 below it). Default 4. */ + columns?: 2 | 3 | 4 | 5 | 6; + /** Muted grayscale logos that regain color on hover. Default true. */ + grayscale?: boolean; + /** Logo height in pixels. Default 32. */ + size?: number; + theme?: "light" | "dark" | "auto"; + /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */ + token?: string; +} + +const COLUMN_CLASSES: Record, string> = { + 2: "sm:grid-cols-2", + 3: "sm:grid-cols-3", + 4: "sm:grid-cols-4", + 5: "sm:grid-cols-5", + 6: "sm:grid-cols-6", +}; + +const normalizeBrand = (brand: LogoWallBrand) => + typeof brand === "string" ? { domain: brand } : brand; + +/** + * A customer or integration logo grid from a list of domains. Server-safe: + * hover color is pure CSS, so it renders fine in React Server Components. + */ +function LogoWall({ + brands, + size = 32, + columns = 4, + grayscale = true, + theme, + attribution = true, + token, + className, + ...props +}: LogoWallProps) { + return ( +
    +
      + {brands.map(normalizeBrand).map(({ domain, name, href }) => { + const logo = ( + + ); + return ( +
    • + {href ? ( + + {logo} + + ) : ( + logo + )} +
    • + ); + })} +
    + {attribution ? ( +
    + +
    + ) : null} +
    + ); +} + +export type { LogoWallBrand, LogoWallProps }; +export { LogoWall }; diff --git a/registry/new-york/hooks/use-brand-search.ts b/registry/new-york/hooks/use-brand-search.ts new file mode 100644 index 0000000..6f91be7 --- /dev/null +++ b/registry/new-york/hooks/use-brand-search.ts @@ -0,0 +1,105 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import type { BrandSearchResult } from "@/registry/new-york/lib/logo-dev"; + +interface UseBrandSearchOptions { + debounceMs?: number; + enabled?: boolean; + /** + * Server endpoint that proxies the Logo.dev Search API with your secret + * key. Defaults to the Next.js route installed with the brand-search + * block; point it at your own proxy on other stacks. + */ + endpoint?: string; + /** Maximum results to keep (the API returns up to 10). Default 8. */ + limit?: number; +} + +const DEFAULT_DEBOUNCE_MS = 200; +const DEFAULT_LIMIT = 8; + +const performSearch = async ( + endpoint: string, + query: string, + limit: number, + signal: AbortSignal +): Promise => { + const response = await fetch(`${endpoint}?q=${encodeURIComponent(query)}`, { + signal, + }); + if (!response.ok) { + throw new Error(`Brand search failed (${response.status})`); + } + const data = (await response.json()) as BrandSearchResult[]; + return data.slice(0, limit); +}; + +const isAbortError = (caught: unknown): boolean => + caught instanceof DOMException && caught.name === "AbortError"; + +/** + * Debounced company search against the Logo.dev Search API. Stale in-flight + * requests are aborted, and previous results stay visible while a new query + * loads so the list doesn't flicker. + */ +function useBrandSearch({ + endpoint = "/api/logo-dev/search", + debounceMs = DEFAULT_DEBOUNCE_MS, + limit = DEFAULT_LIMIT, + enabled = true, +}: UseBrandSearchOptions = {}) { + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const abortRef = useRef(null); + + useEffect(() => { + const trimmed = query.trim(); + if (!(enabled && trimmed)) { + abortRef.current?.abort(); + setResults([]); + setIsLoading(false); + setError(null); + return; + } + + const timer = setTimeout(() => { + abortRef.current?.abort(); + const controller = new AbortController(); + abortRef.current = controller; + setIsLoading(true); + + performSearch(endpoint, trimmed, limit, controller.signal) + .then((data) => { + setResults(data); + setError(null); + }) + .catch((caught: unknown) => { + if (!isAbortError(caught)) { + setError( + caught instanceof Error + ? caught + : new Error("Brand search failed") + ); + } + }) + .finally(() => { + // A newer request may already own the loading state. + if (abortRef.current === controller) { + setIsLoading(false); + } + }); + }, debounceMs); + + return () => clearTimeout(timer); + }, [query, enabled, endpoint, debounceMs, limit]); + + useEffect(() => () => abortRef.current?.abort(), []); + + return { error, isLoading, query, results, setQuery }; +} + +export type { UseBrandSearchOptions }; +export { useBrandSearch }; diff --git a/registry/new-york/lib/logo-dev.test.ts b/registry/new-york/lib/logo-dev.test.ts new file mode 100644 index 0000000..8ffb45b --- /dev/null +++ b/registry/new-york/lib/logo-dev.test.ts @@ -0,0 +1,151 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + getLogoDevToken, + logoDevSrcSet, + logoDevUrl, + logoInitials, + logoSourceValue, + withLogoParams, +} from "./logo-dev"; + +const TOKEN = "pk_test_token"; + +const paramsOf = (url: string) => new URL(url).searchParams; + +beforeEach(() => { + vi.unstubAllEnvs(); +}); + +describe("logoSourceValue", () => { + it("treats a bare string as a domain", () => { + expect(logoSourceValue("stripe.com")).toEqual({ + kind: "domain", + value: "stripe.com", + }); + }); + + it("normalizes each object form to its kind", () => { + expect(logoSourceValue({ ticker: "AAPL" }).kind).toBe("ticker"); + expect(logoSourceValue({ crypto: "btc" }).kind).toBe("crypto"); + expect(logoSourceValue({ isin: "US0378331005" }).kind).toBe("isin"); + expect(logoSourceValue({ name: "Nike" }).kind).toBe("name"); + expect(logoSourceValue({ domain: "nike.com" }).kind).toBe("domain"); + }); +}); + +describe("logoDevUrl", () => { + it("builds a domain URL with defaults", () => { + const url = logoDevUrl("stripe.com", { token: TOKEN }); + expect(url.startsWith("https://img.logo.dev/stripe.com?")).toBe(true); + expect(paramsOf(url).get("token")).toBe(TOKEN); + expect(paramsOf(url).get("size")).toBe("64"); + expect(paramsOf(url).get("format")).toBe("webp"); + }); + + it("prefixes non-domain kinds with their path segment", () => { + expect(logoDevUrl({ ticker: "AAPL" }, { token: TOKEN })).toContain( + "/ticker/AAPL?" + ); + expect(logoDevUrl({ crypto: "btc" }, { token: TOKEN })).toContain( + "/crypto/btc?" + ); + expect(logoDevUrl({ isin: "US0378331005" }, { token: TOKEN })).toContain( + "/isin/US0378331005?" + ); + }); + + it("URL-encodes brand names", () => { + expect(logoDevUrl({ name: "AT&T" }, { token: TOKEN })).toContain( + "/name/AT%26T?" + ); + }); + + it("clamps and rounds size", () => { + const big = logoDevUrl("a.com", { size: 9999, token: TOKEN }); + const small = logoDevUrl("a.com", { size: 0, token: TOKEN }); + const fraction = logoDevUrl("a.com", { size: 24.7, token: TOKEN }); + expect(paramsOf(big).get("size")).toBe("800"); + expect(paramsOf(small).get("size")).toBe("1"); + expect(paramsOf(fraction).get("size")).toBe("25"); + }); + + it("omits theme for auto and sets it for light/dark", () => { + const auto = logoDevUrl("a.com", { theme: "auto", token: TOKEN }); + const dark = logoDevUrl("a.com", { theme: "dark", token: TOKEN }); + expect(paramsOf(auto).has("theme")).toBe(false); + expect(paramsOf(dark).get("theme")).toBe("dark"); + }); + + it("only emits fallback=404 when requested", () => { + const monogram = logoDevUrl("a.com", { token: TOKEN }); + const notFound = logoDevUrl("a.com", { fallback: "404", token: TOKEN }); + expect(paramsOf(monogram).has("fallback")).toBe(false); + expect(paramsOf(notFound).get("fallback")).toBe("404"); + }); + + it("emits greyscale and retina flags when enabled", () => { + const url = logoDevUrl("a.com", { + greyscale: true, + retina: true, + token: TOKEN, + }); + expect(paramsOf(url).get("greyscale")).toBe("true"); + expect(paramsOf(url).get("retina")).toBe("true"); + }); +}); + +describe("getLogoDevToken", () => { + it("prefers an explicit token over the environment", () => { + vi.stubEnv("NEXT_PUBLIC_LOGO_DEV_TOKEN", "pk_from_env"); + expect(getLogoDevToken("pk_explicit")).toBe("pk_explicit"); + expect(getLogoDevToken()).toBe("pk_from_env"); + }); +}); + +describe("logoDevSrcSet", () => { + it("returns a 1x/2x srcSet where only the 2x is retina", () => { + const { src, srcSet } = logoDevSrcSet("stripe.com", { + size: 40, + token: TOKEN, + }); + const [oneX, twoX] = srcSet.split(", "); + expect(oneX?.endsWith(" 1x")).toBe(true); + expect(twoX?.endsWith(" 2x")).toBe(true); + expect(src).toBe(oneX?.slice(0, -" 1x".length)); + expect(paramsOf(src).has("retina")).toBe(false); + expect(paramsOf(twoX?.slice(0, -" 2x".length) ?? "").get("retina")).toBe( + "true" + ); + }); +}); + +describe("withLogoParams", () => { + it("overrides params while keeping the existing token", () => { + const url = withLogoParams(`https://img.logo.dev/nike.com?token=${TOKEN}`, { + format: "webp", + size: 40, + }); + expect(paramsOf(url).get("token")).toBe(TOKEN); + expect(paramsOf(url).get("size")).toBe("40"); + expect(paramsOf(url).get("format")).toBe("webp"); + }); + + it("returns invalid URLs unchanged", () => { + expect(withLogoParams("not a url", { size: 40 })).toBe("not a url"); + }); +}); + +describe("logoInitials", () => { + it("uses the first letters of the first two words", () => { + expect(logoInitials("Andreessen Horowitz")).toBe("AH"); + }); + + it("uses a single letter for single words and domains", () => { + expect(logoInitials("stripe.com")).toBe("S"); + expect(logoInitials("Nike")).toBe("N"); + }); + + it("falls back to ? for empty labels", () => { + expect(logoInitials(" ")).toBe("?"); + }); +}); diff --git a/registry/new-york/lib/logo-dev.ts b/registry/new-york/lib/logo-dev.ts new file mode 100644 index 0000000..69976e4 --- /dev/null +++ b/registry/new-york/lib/logo-dev.ts @@ -0,0 +1,218 @@ +/** + * Logo.dev URL builder and shared types. + * + * Docs: https://docs.logo.dev + * Get a free publishable (pk_) key: https://www.logo.dev/dashboard + */ + +const LOGO_DEV_IMG_BASE = "https://img.logo.dev"; +const MIN_SIZE = 1; +const MAX_SIZE = 800; +const DEFAULT_SIZE = 64; + +/** + * One of the five Logo.dev lookup types. A plain string is treated as a + * domain (`"stripe.com"`). + */ +export type LogoSource = + | { domain: string } + | { name: string } + | { ticker: string } + | { crypto: string } + | { isin: string }; + +export type LogoKind = "domain" | "name" | "ticker" | "crypto" | "isin"; + +export interface LogoUrlOptions { + /** + * What the API returns when no logo exists: `monogram` (default) serves a + * generated letter tile with HTTP 200; `404` returns an HTTP 404 so an + * `onError` handler can render a local fallback instead. + */ + fallback?: "monogram" | "404"; + /** Image format. `svg` requires an Enterprise plan — never used by default. */ + format?: "webp" | "png" | "jpg" | "svg"; + /** Return a black-and-white version of the logo. */ + greyscale?: boolean; + /** Render at 2× the requested size for high-density displays. */ + retina?: boolean; + /** Logical pixel size (width and height). Clamped to 1–800. Default 64. */ + size?: number; + /** + * `light` inverts dark logos for light backgrounds, `dark` inverts light + * logos for dark backgrounds. `auto` (default) requests the logo as-is. + */ + theme?: "light" | "dark" | "auto"; + /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */ + token?: string; +} + +/** A result row from the Logo.dev Brand Search API. */ +export interface BrandSearchResult { + domain: string; + logo_url: string; + name: string; +} + +let warnedMissingToken = false; + +const envToken = (): string | undefined => { + if (typeof process === "undefined") { + // Non-Node bundlers (e.g. Vite) don't define `process`; pass `token` instead. + return; + } + return process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN; +}; + +/** + * Resolve the publishable API token: explicit argument first, then the + * NEXT_PUBLIC_LOGO_DEV_TOKEN environment variable. + */ +export const getLogoDevToken = (token?: string): string => { + const resolved = token ?? envToken() ?? ""; + if (!(resolved || warnedMissingToken) && typeof console !== "undefined") { + warnedMissingToken = true; + console.warn( + "[logo.dev] No API token found. Set NEXT_PUBLIC_LOGO_DEV_TOKEN or pass `token`. Get a free key at https://www.logo.dev/dashboard" + ); + } + return resolved; +}; + +/** Normalize a LogoSource (or bare domain string) into its kind and value. */ +export const logoSourceValue = ( + source: LogoSource | string +): { kind: LogoKind; value: string } => { + if (typeof source === "string") { + return { kind: "domain", value: source }; + } + if ("domain" in source) { + return { kind: "domain", value: source.domain }; + } + if ("name" in source) { + return { kind: "name", value: source.name }; + } + if ("ticker" in source) { + return { kind: "ticker", value: source.ticker }; + } + if ("crypto" in source) { + return { kind: "crypto", value: source.crypto }; + } + return { kind: "isin", value: source.isin }; +}; + +const buildPath = (kind: LogoKind, value: string): string => { + const encoded = encodeURIComponent(value.trim()); + return kind === "domain" ? encoded : `${kind}/${encoded}`; +}; + +const clampSize = (size: number): number => { + if (!Number.isFinite(size)) { + return DEFAULT_SIZE; + } + return Math.min(MAX_SIZE, Math.max(MIN_SIZE, Math.round(size))); +}; + +/** Build an img.logo.dev URL for any lookup type. */ +export const logoDevUrl = ( + source: LogoSource | string, + options: LogoUrlOptions = {} +): string => { + const { kind, value } = logoSourceValue(source); + const { + size = DEFAULT_SIZE, + format = "webp", + theme, + greyscale, + retina, + fallback, + token, + } = options; + + const params = new URLSearchParams(); + params.set("token", getLogoDevToken(token)); + params.set("size", String(clampSize(size))); + params.set("format", format); + if (theme === "light" || theme === "dark") { + params.set("theme", theme); + } + if (greyscale) { + params.set("greyscale", "true"); + } + if (retina) { + params.set("retina", "true"); + } + if (fallback === "404") { + params.set("fallback", "404"); + } + + return `${LOGO_DEV_IMG_BASE}/${buildPath(kind, value)}?${params.toString()}`; +}; + +/** + * Build `src` and a 1x/2x `srcSet` for crisp logos on high-density displays. + * The 2x candidate uses the API's `retina` parameter, which renders at twice + * the requested size. + */ +export const logoDevSrcSet = ( + source: LogoSource | string, + options: LogoUrlOptions = {} +): { src: string; srcSet: string } => { + const src = logoDevUrl(source, { ...options, retina: false }); + const src2x = logoDevUrl(source, { ...options, retina: true }); + return { src, srcSet: `${src} 1x, ${src2x} 2x` }; +}; + +/** + * Append or override image parameters on an existing img.logo.dev URL, such + * as the `logo_url` returned by the Brand Search API (which already carries + * a token). Returns the input unchanged if it isn't a valid URL. + */ +export const withLogoParams = ( + logoUrl: string, + options: Omit = {} +): string => { + let url: URL; + try { + url = new URL(logoUrl); + } catch { + return logoUrl; + } + const { size, format, theme, greyscale, retina, fallback } = options; + if (size !== undefined) { + url.searchParams.set("size", String(clampSize(size))); + } + if (format) { + url.searchParams.set("format", format); + } + if (theme === "light" || theme === "dark") { + url.searchParams.set("theme", theme); + } + if (greyscale !== undefined) { + url.searchParams.set("greyscale", greyscale ? "true" : "false"); + } + if (retina !== undefined) { + url.searchParams.set("retina", retina ? "true" : "false"); + } + if (fallback === "404") { + url.searchParams.set("fallback", "404"); + } + return url.toString(); +}; + +const WORD_SEPARATORS = /[\s\-_]+/; + +/** + * Initials for a local fallback tile: first letters of the first two words + * ("Andreessen Horowitz" → "AH"), or the first character for single words + * and domains ("stripe.com" → "S"). + */ +export const logoInitials = (label: string): string => { + const words = label.trim().split(WORD_SEPARATORS).filter(Boolean); + const first = words.at(0)?.at(0); + if (!first) { + return "?"; + } + const second = words.length > 1 ? words.at(1)?.at(0) : undefined; + return `${first}${second ?? ""}`.toUpperCase(); +}; diff --git a/registry/new-york/ui/attribution.tsx b/registry/new-york/ui/attribution.tsx new file mode 100644 index 0000000..7af6f53 --- /dev/null +++ b/registry/new-york/ui/attribution.tsx @@ -0,0 +1,38 @@ +import type { ComponentProps } from "react"; +import { cn } from "@/lib/utils"; + +interface AttributionProps + extends Omit, "href" | "children"> { + label?: string; +} + +/** + * Attribution link required on Logo.dev free plans in production. + * See https://docs.logo.dev/platform/attribution + * + * Uses rel="noopener" without "noreferrer" on purpose: the referrer is how + * attribution is verified. + */ +function Attribution({ + label = "Logos provided by Logo.dev", + className, + ...props +}: AttributionProps) { + return ( + + {label} + + ); +} + +export type { AttributionProps }; +export { Attribution }; diff --git a/registry/new-york/ui/logo-avatar.tsx b/registry/new-york/ui/logo-avatar.tsx new file mode 100644 index 0000000..c33c931 --- /dev/null +++ b/registry/new-york/ui/logo-avatar.tsx @@ -0,0 +1,71 @@ +"use client"; + +import type { ComponentProps } from "react"; +import { Avatar } from "@/components/ui/avatar"; +import { cn } from "@/lib/utils"; +import { Logo, type LogoSourceProps } from "@/registry/new-york/ui/logo"; + +type LogoAvatarProps = LogoSourceProps & + Omit, "children"> & { + /** Avatar dimension in pixels. Default 32. */ + size?: number; + /** Display name used for alt text and initials (e.g. "Apple" for ticker AAPL). */ + label?: string; + theme?: "light" | "dark" | "auto"; + greyscale?: boolean; + /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */ + token?: string; + }; + +/** + * A company logo in a shadcn Avatar shell with an initials fallback — for + * CRM rows, transaction feeds, and anywhere you'd show a user avatar. + * + * Defaults to rounded-md: full circles crop most logos. Pass + * className="rounded-full" if you want circular avatars anyway. + */ +function LogoAvatar({ + domain, + name, + ticker, + crypto, + isin, + size = 32, + label, + theme, + greyscale, + token, + className, + style, + ...avatarProps +}: LogoAvatarProps) { + const sourceProps = { + crypto, + domain, + isin, + name, + ticker, + } as LogoSourceProps; + + return ( + + + + ); +} + +export type { LogoAvatarProps }; +export { LogoAvatar }; diff --git a/registry/new-york/ui/logo.tsx b/registry/new-york/ui/logo.tsx new file mode 100644 index 0000000..4793840 --- /dev/null +++ b/registry/new-york/ui/logo.tsx @@ -0,0 +1,289 @@ +"use client"; + +import { + type ComponentProps, + type CSSProperties, + type ReactEventHandler, + type ReactNode, + useState, +} from "react"; +import { cn } from "@/lib/utils"; +import { + type LogoSource, + type LogoUrlOptions, + logoDevSrcSet, + logoDevUrl, + logoInitials, + logoSourceValue, +} from "@/registry/new-york/lib/logo-dev"; + +/** Exactly one lookup identifier: domain, name, ticker, crypto, or isin. */ +type LogoSourceProps = + | { + domain: string; + name?: never; + ticker?: never; + crypto?: never; + isin?: never; + } + | { + name: string; + domain?: never; + ticker?: never; + crypto?: never; + isin?: never; + } + | { + ticker: string; + domain?: never; + name?: never; + crypto?: never; + isin?: never; + } + | { + crypto: string; + domain?: never; + name?: never; + ticker?: never; + isin?: never; + } + | { + isin: string; + domain?: never; + name?: never; + ticker?: never; + crypto?: never; + }; + +type LogoProps = LogoSourceProps & + Omit, "src" | "srcSet" | "children"> & { + /** Logical pixel size; sets width and height so the layout never shifts. */ + size?: number; + /** Image format. `svg` requires an Enterprise plan. */ + format?: "webp" | "png" | "jpg" | "svg"; + /** + * `auto` (default) renders light and dark variants toggled by the `dark:` + * Tailwind variant, so the right logo shows in both color schemes with + * no theme provider. `light`/`dark` render a single variant. + */ + theme?: "light" | "dark" | "auto"; + greyscale?: boolean; + /** Serve a 1x/2x srcSet for high-density displays. Default true. */ + retina?: boolean; + /** + * What renders when no logo exists: `monogram` (default) shows the + * API-generated letter tile, `initials` shows a local initials tile, or + * pass any React node for a custom fallback. + */ + fallback?: "monogram" | "initials" | ReactNode; + /** Display name used for alt text and initials (e.g. "Apple" for ticker AAPL). */ + label?: string; + /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */ + token?: string; + }; + +const MIN_TILE_FONT_PX = 10; +const TILE_FONT_RATIO = 0.4; + +interface InitialsTileProps { + className?: string; + height: number | string; + label: string; + style?: CSSProperties; + width: number | string; +} + +const InitialsTile = ({ + label, + width, + height, + className, + style, +}: InitialsTileProps) => { + const numericHeight = typeof height === "number" ? height : undefined; + const fontSize = numericHeight + ? Math.max(MIN_TILE_FONT_PX, Math.round(numericHeight * TILE_FONT_RATIO)) + : undefined; + return ( + + {logoInitials(label)} + + ); +}; + +const toLogoSource = (parts: { + domain?: string; + name?: string; + ticker?: string; + crypto?: string; + isin?: string; +}): LogoSource => { + if (parts.name !== undefined) { + return { name: parts.name }; + } + if (parts.ticker !== undefined) { + return { ticker: parts.ticker }; + } + if (parts.crypto !== undefined) { + return { crypto: parts.crypto }; + } + if (parts.isin !== undefined) { + return { isin: parts.isin }; + } + return { domain: parts.domain ?? "" }; +}; + +/** + * A company logo from Logo.dev that never breaks: retina srcSet, automatic + * dark-mode variants, and layered fallbacks (API monogram, local initials, + * or a custom node). + */ +function Logo(props: LogoProps) { + const { + domain, + name, + ticker, + crypto, + isin, + size = 32, + format, + theme = "auto", + greyscale, + retina = true, + fallback = "monogram", + label, + token, + alt, + className, + width, + height, + style, + loading = "lazy", + decoding = "async", + onError, + ...imgProps + } = props; + + const source = toLogoSource({ crypto, domain, isin, name, ticker }); + const { kind, value } = logoSourceValue(source); + const labelText = label ?? name ?? value; + + // The error flag must reset when the logo identity changes, otherwise a + // fallback for one company would stick around after switching to another. + const identityKey = `${kind}:${value}:${token ?? ""}`; + const [errored, setErrored] = useState(false); + const [prevKey, setPrevKey] = useState(identityKey); + if (prevKey !== identityKey) { + setPrevKey(identityKey); + setErrored(false); + } + + // The API returns HTTP 200 with a generated monogram on a miss, so onError + // only ever fires for a missing logo when the URL requests fallback=404. + const wantsLocalFallback = fallback !== "monogram"; + const urlFallback: LogoUrlOptions["fallback"] = wantsLocalFallback + ? "404" + : undefined; + + const resolvedWidth = width ?? size; + const resolvedHeight = height ?? size; + + if (errored) { + if (fallback === "monogram" || fallback === "initials") { + return ( + + ); + } + return <>{fallback}; + } + + const sharedOptions: LogoUrlOptions = { + fallback: urlFallback, + format, + greyscale, + size, + token, + }; + + const srcFor = (variant?: "light" | "dark") => { + const options = { ...sharedOptions, theme: variant }; + if (retina) { + return logoDevSrcSet(source, options); + } + return { + src: logoDevUrl(source, { ...options, retina: false }), + srcSet: undefined, + }; + }; + + const handleError: ReactEventHandler = (event) => { + setErrored(true); + onError?.(event); + }; + + const shared = { + decoding, + loading, + onError: handleError, + style, + ...imgProps, + }; + const resolvedAlt = alt ?? `${labelText} logo`; + + if (theme !== "auto") { + const { src, srcSet } = srcFor(theme); + return ( + {resolvedAlt} + ); + } + + const light = srcFor("light"); + const dark = srcFor("dark"); + return ( + <> + {resolvedAlt} + {resolvedAlt} + + ); +} + +export type { LogoProps, LogoSourceProps }; +export { Logo }; diff --git a/styles/globals.css b/styles/globals.css new file mode 100644 index 0000000..b3409c1 --- /dev/null +++ b/styles/globals.css @@ -0,0 +1,6 @@ +/* + * Stub stylesheet. This repo distributes registry source files; it does not + * ship or build CSS. The path exists so the shadcn CLI has a valid + * `tailwind.css` entry in components.json. + */ +@import "tailwindcss"; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d3c0c0f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "paths": { + "@/*": ["./*"] + } + }, + "include": ["registry", "components", "lib", "hooks", "vitest.config.ts"], + "exclude": ["node_modules", "r"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..9f6c584 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["registry/**/*.test.ts"], + }, +}); From 5d2257041126ffafa0bd9084a94c4559a590d68e Mon Sep 17 00:00:00 2001 From: Alex Baldwin Date: Mon, 6 Jul 2026 11:00:25 -0400 Subject: [PATCH 2/4] Omit the Avatar size prop from LogoAvatar Some shadcn Avatar variants (e.g. radix-nova) define their own string size variant, which intersected with our numeric size prop to never. Caught installing into a radix-nova consumer app. Co-Authored-By: Claude Fable 5 --- r/logo-avatar.json | 2 +- registry/new-york/ui/logo-avatar.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/r/logo-avatar.json b/r/logo-avatar.json index 9a7bf63..474f609 100644 --- a/r/logo-avatar.json +++ b/r/logo-avatar.json @@ -11,7 +11,7 @@ "files": [ { "path": "registry/new-york/ui/logo-avatar.tsx", - "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Avatar } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport { Logo, type LogoSourceProps } from \"@/registry/new-york/ui/logo\";\n\ntype LogoAvatarProps = LogoSourceProps &\n Omit, \"children\"> & {\n /** Avatar dimension in pixels. Default 32. */\n size?: number;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\n/**\n * A company logo in a shadcn Avatar shell with an initials fallback — for\n * CRM rows, transaction feeds, and anywhere you'd show a user avatar.\n *\n * Defaults to rounded-md: full circles crop most logos. Pass\n * className=\"rounded-full\" if you want circular avatars anyway.\n */\nfunction LogoAvatar({\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n label,\n theme,\n greyscale,\n token,\n className,\n style,\n ...avatarProps\n}: LogoAvatarProps) {\n const sourceProps = {\n crypto,\n domain,\n isin,\n name,\n ticker,\n } as LogoSourceProps;\n\n return (\n \n \n \n );\n}\n\nexport type { LogoAvatarProps };\nexport { LogoAvatar };\n", + "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { Avatar } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\nimport { Logo, type LogoSourceProps } from \"@/registry/new-york/ui/logo\";\n\ntype LogoAvatarProps = LogoSourceProps &\n // `size` is omitted because some shadcn Avatar variants define their own\n // (string) size prop; ours is the pixel dimension below.\n Omit, \"children\" | \"size\"> & {\n /** Avatar dimension in pixels. Default 32. */\n size?: number;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\n/**\n * A company logo in a shadcn Avatar shell with an initials fallback — for\n * CRM rows, transaction feeds, and anywhere you'd show a user avatar.\n *\n * Defaults to rounded-md: full circles crop most logos. Pass\n * className=\"rounded-full\" if you want circular avatars anyway.\n */\nfunction LogoAvatar({\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n label,\n theme,\n greyscale,\n token,\n className,\n style,\n ...avatarProps\n}: LogoAvatarProps) {\n const sourceProps = {\n crypto,\n domain,\n isin,\n name,\n ticker,\n } as LogoSourceProps;\n\n return (\n \n \n \n );\n}\n\nexport type { LogoAvatarProps };\nexport { LogoAvatar };\n", "type": "registry:ui" } ], diff --git a/registry/new-york/ui/logo-avatar.tsx b/registry/new-york/ui/logo-avatar.tsx index c33c931..c2e44cb 100644 --- a/registry/new-york/ui/logo-avatar.tsx +++ b/registry/new-york/ui/logo-avatar.tsx @@ -6,7 +6,9 @@ import { cn } from "@/lib/utils"; import { Logo, type LogoSourceProps } from "@/registry/new-york/ui/logo"; type LogoAvatarProps = LogoSourceProps & - Omit, "children"> & { + // `size` is omitted because some shadcn Avatar variants define their own + // (string) size prop; ours is the pixel dimension below. + Omit, "children" | "size"> & { /** Avatar dimension in pixels. Default 32. */ size?: number; /** Display name used for alt text and initials (e.g. "Apple" for ticker AAPL). */ From aa09998ddec7160dc99fae929a492a1e891b1926 Mon Sep 17 00:00:00 2001 From: Alex Baldwin Date: Mon, 6 Jul 2026 12:53:05 -0400 Subject: [PATCH 3/4] Inline the brand-search chevron so it needs no icon library Codex review (P2): brand-search.tsx imported ChevronsUpDownIcon from lucide-react, but the registry item never installed that package. A consumer whose project lacks lucide-react (e.g. initialized with a different icon library, or none) would get code that fails to compile. shadcn's own primitives import lucide directly because `shadcn init` always installs it, and its registry transform only rewrites an explicit element per the consumer's configured iconLibrary. Since this block uses exactly one decorative icon, inline it as a self-contained SVG instead: zero dependencies, works on any icon setup, nothing to declare in registry.json. Co-Authored-By: Claude Fable 5 --- r/brand-search.json | 2 +- .../blocks/brand-search/brand-search.tsx | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/r/brand-search.json b/r/brand-search.json index def669d..21ec579 100644 --- a/r/brand-search.json +++ b/r/brand-search.json @@ -13,7 +13,7 @@ "files": [ { "path": "registry/new-york/blocks/brand-search/brand-search.tsx", - "content": "\"use client\";\n\nimport { ChevronsUpDownIcon } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Command,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { useBrandSearch } from \"@/registry/new-york/hooks/use-brand-search\";\nimport {\n type BrandSearchResult,\n withLogoParams,\n} from \"@/registry/new-york/lib/logo-dev\";\n\ninterface BrandSearchProps {\n className?: string;\n defaultValue?: BrandSearchResult | null;\n disabled?: boolean;\n /** Server endpoint proxying the Logo.dev Search API. */\n endpoint?: string;\n /** Maximum results shown. Default 8. */\n limit?: number;\n onSelect?: (brand: BrandSearchResult) => void;\n placeholder?: string;\n /** Controlled selection. Leave undefined for uncontrolled use. */\n value?: BrandSearchResult | null;\n}\n\nconst RESULT_LOGO_PX = 20;\n\nconst resultLogoUrl = (brand: BrandSearchResult) =>\n // logo_url already carries a token; just request a crisp, small render.\n withLogoParams(brand.logo_url, {\n format: \"webp\",\n size: RESULT_LOGO_PX * 2,\n });\n\nconst BrandSearchResultItem = ({\n brand,\n onSelect,\n}: {\n brand: BrandSearchResult;\n onSelect: (brand: BrandSearchResult) => void;\n}) => {\n const handleSelect = useCallback(() => {\n onSelect(brand);\n }, [brand, onSelect]);\n return (\n \n \n {brand.name}\n \n {brand.domain}\n \n \n );\n};\n\n/**\n * Company autocomplete backed by the Logo.dev Search API — pick a company by\n * name and get its domain and logo. Requires the API route installed with\n * this block (your sk_ key stays on the server).\n */\nfunction BrandSearch({\n value,\n defaultValue,\n onSelect,\n endpoint,\n placeholder = \"Search for a company…\",\n limit,\n disabled,\n className,\n}: BrandSearchProps) {\n const [open, setOpen] = useState(false);\n const [internal, setInternal] = useState(\n defaultValue ?? null\n );\n const selected = value === undefined ? internal : value;\n\n const { query, setQuery, results, isLoading, error } = useBrandSearch({\n enabled: open,\n endpoint,\n limit,\n });\n\n const handleSelect = useCallback(\n (brand: BrandSearchResult) => {\n if (value === undefined) {\n setInternal(brand);\n }\n onSelect?.(brand);\n setOpen(false);\n },\n [value, onSelect]\n );\n\n const showStatus = results.length === 0;\n let status: string | null = null;\n if (showStatus && isLoading) {\n status = \"Searching…\";\n } else if (showStatus && error) {\n status = \"Something went wrong. Try again.\";\n } else if (showStatus && query.trim()) {\n status = \"No companies found.\";\n }\n\n return (\n \n \n \n {selected ? (\n \n \n {selected.name}\n \n ) : (\n {placeholder}\n )}\n \n \n \n \n {/* The API filters server-side; local cmdk filtering must stay off. */}\n \n \n \n {status ? (\n

    \n {status}\n

    \n ) : (\n \n {results.map((brand) => (\n \n ))}\n \n )}\n
    \n
    \n \n
    \n );\n}\n\nexport type { BrandSearchProps };\nexport { BrandSearch };\n", + "content": "\"use client\";\n\nimport { type SVGProps, useCallback, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Command,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { useBrandSearch } from \"@/registry/new-york/hooks/use-brand-search\";\nimport {\n type BrandSearchResult,\n withLogoParams,\n} from \"@/registry/new-york/lib/logo-dev\";\n\n// Inlined so the component needs no icon library. Consumers on any icon\n// setup (lucide, radix, none) install this without extra dependencies.\nconst ChevronsUpDownIcon = (props: SVGProps) => (\n \n \n \n \n);\n\ninterface BrandSearchProps {\n className?: string;\n defaultValue?: BrandSearchResult | null;\n disabled?: boolean;\n /** Server endpoint proxying the Logo.dev Search API. */\n endpoint?: string;\n /** Maximum results shown. Default 8. */\n limit?: number;\n onSelect?: (brand: BrandSearchResult) => void;\n placeholder?: string;\n /** Controlled selection. Leave undefined for uncontrolled use. */\n value?: BrandSearchResult | null;\n}\n\nconst RESULT_LOGO_PX = 20;\n\nconst resultLogoUrl = (brand: BrandSearchResult) =>\n // logo_url already carries a token; just request a crisp, small render.\n withLogoParams(brand.logo_url, {\n format: \"webp\",\n size: RESULT_LOGO_PX * 2,\n });\n\nconst BrandSearchResultItem = ({\n brand,\n onSelect,\n}: {\n brand: BrandSearchResult;\n onSelect: (brand: BrandSearchResult) => void;\n}) => {\n const handleSelect = useCallback(() => {\n onSelect(brand);\n }, [brand, onSelect]);\n return (\n \n \n {brand.name}\n \n {brand.domain}\n \n \n );\n};\n\n/**\n * Company autocomplete backed by the Logo.dev Search API — pick a company by\n * name and get its domain and logo. Requires the API route installed with\n * this block (your sk_ key stays on the server).\n */\nfunction BrandSearch({\n value,\n defaultValue,\n onSelect,\n endpoint,\n placeholder = \"Search for a company…\",\n limit,\n disabled,\n className,\n}: BrandSearchProps) {\n const [open, setOpen] = useState(false);\n const [internal, setInternal] = useState(\n defaultValue ?? null\n );\n const selected = value === undefined ? internal : value;\n\n const { query, setQuery, results, isLoading, error } = useBrandSearch({\n enabled: open,\n endpoint,\n limit,\n });\n\n const handleSelect = useCallback(\n (brand: BrandSearchResult) => {\n if (value === undefined) {\n setInternal(brand);\n }\n onSelect?.(brand);\n setOpen(false);\n },\n [value, onSelect]\n );\n\n const showStatus = results.length === 0;\n let status: string | null = null;\n if (showStatus && isLoading) {\n status = \"Searching…\";\n } else if (showStatus && error) {\n status = \"Something went wrong. Try again.\";\n } else if (showStatus && query.trim()) {\n status = \"No companies found.\";\n }\n\n return (\n \n \n \n {selected ? (\n \n \n {selected.name}\n \n ) : (\n {placeholder}\n )}\n \n \n \n \n {/* The API filters server-side; local cmdk filtering must stay off. */}\n \n \n \n {status ? (\n

    \n {status}\n

    \n ) : (\n \n {results.map((brand) => (\n \n ))}\n \n )}\n
    \n
    \n \n
    \n );\n}\n\nexport type { BrandSearchProps };\nexport { BrandSearch };\n", "type": "registry:component" }, { diff --git a/registry/new-york/blocks/brand-search/brand-search.tsx b/registry/new-york/blocks/brand-search/brand-search.tsx index c1bacf9..ec84129 100644 --- a/registry/new-york/blocks/brand-search/brand-search.tsx +++ b/registry/new-york/blocks/brand-search/brand-search.tsx @@ -1,7 +1,6 @@ "use client"; -import { ChevronsUpDownIcon } from "lucide-react"; -import { useCallback, useState } from "react"; +import { type SVGProps, useCallback, useState } from "react"; import { Button } from "@/components/ui/button"; import { Command, @@ -22,6 +21,27 @@ import { withLogoParams, } from "@/registry/new-york/lib/logo-dev"; +// Inlined so the component needs no icon library. Consumers on any icon +// setup (lucide, radix, none) install this without extra dependencies. +const ChevronsUpDownIcon = (props: SVGProps) => ( + +); + interface BrandSearchProps { className?: string; defaultValue?: BrandSearchResult | null; From 93cf27714b727fe66e31a15dacb1922bc37a4b1b Mon Sep 17 00:00:00 2001 From: Alex Baldwin Date: Mon, 6 Jul 2026 13:31:55 -0400 Subject: [PATCH 4/4] Address code review findings across the registry components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P1 (logo-dev.ts): reading process.env for the token broke TypeScript in browser-only consumers without @types/node ("Cannot find name 'process'"). Add a minimal ambient `declare const process` so the file typechecks with or without @types/node. Verified the literal process.env.NEXT_PUBLIC_* access still gets inlined into Next.js client bundles. - use-brand-search.ts: fix a stale-results race — the in-flight request is now aborted in effect cleanup (not only when the next debounce fires) and state updates are guarded on the controller, so an older query's response can't overwrite the current results. Also fix endpoint query-string joining so a custom endpoint with existing params (?tenant=acme) stays valid. - logo.tsx: respect a decorative alt="" on the initials fallback tile (aria-hidden instead of an announced role=img), and reset the errored flag on the full request identity (fallback/format/theme/greyscale/size), so changing any URL option retries the image instead of sticking on a fallback. - logo-dev.ts: use index access instead of Array.at() in logoInitials for broader TS lib-target portability. Verified: typecheck, lint, 16 unit tests, registry build, a fresh Next.js consumer install (logos render, dead-domain initials fallback, debounced search), non-Node typecheck, and Next client-bundle token inlining. Co-Authored-By: Claude Fable 5 --- r/brand-search.json | 2 +- r/logo-lib.json | 2 +- r/logo.json | 2 +- registry/new-york/hooks/use-brand-search.ts | 38 ++++++++++--------- registry/new-york/lib/logo-dev.ts | 22 ++++++----- registry/new-york/ui/logo.tsx | 42 +++++++++++++++------ 6 files changed, 67 insertions(+), 41 deletions(-) diff --git a/r/brand-search.json b/r/brand-search.json index 21ec579..436f836 100644 --- a/r/brand-search.json +++ b/r/brand-search.json @@ -18,7 +18,7 @@ }, { "path": "registry/new-york/hooks/use-brand-search.ts", - "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport type { BrandSearchResult } from \"@/registry/new-york/lib/logo-dev\";\n\ninterface UseBrandSearchOptions {\n debounceMs?: number;\n enabled?: boolean;\n /**\n * Server endpoint that proxies the Logo.dev Search API with your secret\n * key. Defaults to the Next.js route installed with the brand-search\n * block; point it at your own proxy on other stacks.\n */\n endpoint?: string;\n /** Maximum results to keep (the API returns up to 10). Default 8. */\n limit?: number;\n}\n\nconst DEFAULT_DEBOUNCE_MS = 200;\nconst DEFAULT_LIMIT = 8;\n\nconst performSearch = async (\n endpoint: string,\n query: string,\n limit: number,\n signal: AbortSignal\n): Promise => {\n const response = await fetch(`${endpoint}?q=${encodeURIComponent(query)}`, {\n signal,\n });\n if (!response.ok) {\n throw new Error(`Brand search failed (${response.status})`);\n }\n const data = (await response.json()) as BrandSearchResult[];\n return data.slice(0, limit);\n};\n\nconst isAbortError = (caught: unknown): boolean =>\n caught instanceof DOMException && caught.name === \"AbortError\";\n\n/**\n * Debounced company search against the Logo.dev Search API. Stale in-flight\n * requests are aborted, and previous results stay visible while a new query\n * loads so the list doesn't flicker.\n */\nfunction useBrandSearch({\n endpoint = \"/api/logo-dev/search\",\n debounceMs = DEFAULT_DEBOUNCE_MS,\n limit = DEFAULT_LIMIT,\n enabled = true,\n}: UseBrandSearchOptions = {}) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState(null);\n const abortRef = useRef(null);\n\n useEffect(() => {\n const trimmed = query.trim();\n if (!(enabled && trimmed)) {\n abortRef.current?.abort();\n setResults([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const timer = setTimeout(() => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n setIsLoading(true);\n\n performSearch(endpoint, trimmed, limit, controller.signal)\n .then((data) => {\n setResults(data);\n setError(null);\n })\n .catch((caught: unknown) => {\n if (!isAbortError(caught)) {\n setError(\n caught instanceof Error\n ? caught\n : new Error(\"Brand search failed\")\n );\n }\n })\n .finally(() => {\n // A newer request may already own the loading state.\n if (abortRef.current === controller) {\n setIsLoading(false);\n }\n });\n }, debounceMs);\n\n return () => clearTimeout(timer);\n }, [query, enabled, endpoint, debounceMs, limit]);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n return { error, isLoading, query, results, setQuery };\n}\n\nexport type { UseBrandSearchOptions };\nexport { useBrandSearch };\n", + "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { BrandSearchResult } from \"@/registry/new-york/lib/logo-dev\";\n\ninterface UseBrandSearchOptions {\n debounceMs?: number;\n enabled?: boolean;\n /**\n * Server endpoint that proxies the Logo.dev Search API with your secret\n * key. Defaults to the Next.js route installed with the brand-search\n * block; point it at your own proxy on other stacks.\n */\n endpoint?: string;\n /** Maximum results to keep (the API returns up to 10). Default 8. */\n limit?: number;\n}\n\nconst DEFAULT_DEBOUNCE_MS = 200;\nconst DEFAULT_LIMIT = 8;\n\nconst performSearch = async (\n endpoint: string,\n query: string,\n limit: number,\n signal: AbortSignal\n): Promise => {\n // Append `q` with the right separator so a custom endpoint that already has\n // query params (e.g. \"/api/search?tenant=acme\") stays valid.\n const separator = endpoint.includes(\"?\") ? \"&\" : \"?\";\n const response = await fetch(\n `${endpoint}${separator}q=${encodeURIComponent(query)}`,\n { signal }\n );\n if (!response.ok) {\n throw new Error(`Brand search failed (${response.status})`);\n }\n const data = (await response.json()) as BrandSearchResult[];\n return data.slice(0, limit);\n};\n\nconst isAbortError = (caught: unknown): boolean =>\n caught instanceof DOMException && caught.name === \"AbortError\";\n\n/**\n * Debounced company search against the Logo.dev Search API. Stale in-flight\n * requests are aborted, and previous results stay visible while a new query\n * loads so the list doesn't flicker.\n */\nfunction useBrandSearch({\n endpoint = \"/api/logo-dev/search\",\n debounceMs = DEFAULT_DEBOUNCE_MS,\n limit = DEFAULT_LIMIT,\n enabled = true,\n}: UseBrandSearchOptions = {}) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const trimmed = query.trim();\n if (!(enabled && trimmed)) {\n setResults([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n // One controller per query. Cleanup aborts it, so an in-flight request for\n // a previous query can never overwrite results for the current one, even if\n // it resolves during the next debounce window.\n const controller = new AbortController();\n const timer = setTimeout(() => {\n setIsLoading(true);\n performSearch(endpoint, trimmed, limit, controller.signal)\n .then((data) => {\n if (!controller.signal.aborted) {\n setResults(data);\n setError(null);\n }\n })\n .catch((caught: unknown) => {\n if (!isAbortError(caught)) {\n setError(\n caught instanceof Error\n ? caught\n : new Error(\"Brand search failed\")\n );\n }\n })\n .finally(() => {\n if (!controller.signal.aborted) {\n setIsLoading(false);\n }\n });\n }, debounceMs);\n\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n }, [query, enabled, endpoint, debounceMs, limit]);\n\n return { error, isLoading, query, results, setQuery };\n}\n\nexport type { UseBrandSearchOptions };\nexport { useBrandSearch };\n", "type": "registry:hook" }, { diff --git a/r/logo-lib.json b/r/logo-lib.json index e79bbcf..25be0b5 100644 --- a/r/logo-lib.json +++ b/r/logo-lib.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/new-york/lib/logo-dev.ts", - "content": "/**\n * Logo.dev URL builder and shared types.\n *\n * Docs: https://docs.logo.dev\n * Get a free publishable (pk_) key: https://www.logo.dev/dashboard\n */\n\nconst LOGO_DEV_IMG_BASE = \"https://img.logo.dev\";\nconst MIN_SIZE = 1;\nconst MAX_SIZE = 800;\nconst DEFAULT_SIZE = 64;\n\n/**\n * One of the five Logo.dev lookup types. A plain string is treated as a\n * domain (`\"stripe.com\"`).\n */\nexport type LogoSource =\n | { domain: string }\n | { name: string }\n | { ticker: string }\n | { crypto: string }\n | { isin: string };\n\nexport type LogoKind = \"domain\" | \"name\" | \"ticker\" | \"crypto\" | \"isin\";\n\nexport interface LogoUrlOptions {\n /**\n * What the API returns when no logo exists: `monogram` (default) serves a\n * generated letter tile with HTTP 200; `404` returns an HTTP 404 so an\n * `onError` handler can render a local fallback instead.\n */\n fallback?: \"monogram\" | \"404\";\n /** Image format. `svg` requires an Enterprise plan — never used by default. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /** Return a black-and-white version of the logo. */\n greyscale?: boolean;\n /** Render at 2× the requested size for high-density displays. */\n retina?: boolean;\n /** Logical pixel size (width and height). Clamped to 1–800. Default 64. */\n size?: number;\n /**\n * `light` inverts dark logos for light backgrounds, `dark` inverts light\n * logos for dark backgrounds. `auto` (default) requests the logo as-is.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n}\n\n/** A result row from the Logo.dev Brand Search API. */\nexport interface BrandSearchResult {\n domain: string;\n logo_url: string;\n name: string;\n}\n\nlet warnedMissingToken = false;\n\nconst envToken = (): string | undefined => {\n if (typeof process === \"undefined\") {\n // Non-Node bundlers (e.g. Vite) don't define `process`; pass `token` instead.\n return;\n }\n return process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN;\n};\n\n/**\n * Resolve the publishable API token: explicit argument first, then the\n * NEXT_PUBLIC_LOGO_DEV_TOKEN environment variable.\n */\nexport const getLogoDevToken = (token?: string): string => {\n const resolved = token ?? envToken() ?? \"\";\n if (!(resolved || warnedMissingToken) && typeof console !== \"undefined\") {\n warnedMissingToken = true;\n console.warn(\n \"[logo.dev] No API token found. Set NEXT_PUBLIC_LOGO_DEV_TOKEN or pass `token`. Get a free key at https://www.logo.dev/dashboard\"\n );\n }\n return resolved;\n};\n\n/** Normalize a LogoSource (or bare domain string) into its kind and value. */\nexport const logoSourceValue = (\n source: LogoSource | string\n): { kind: LogoKind; value: string } => {\n if (typeof source === \"string\") {\n return { kind: \"domain\", value: source };\n }\n if (\"domain\" in source) {\n return { kind: \"domain\", value: source.domain };\n }\n if (\"name\" in source) {\n return { kind: \"name\", value: source.name };\n }\n if (\"ticker\" in source) {\n return { kind: \"ticker\", value: source.ticker };\n }\n if (\"crypto\" in source) {\n return { kind: \"crypto\", value: source.crypto };\n }\n return { kind: \"isin\", value: source.isin };\n};\n\nconst buildPath = (kind: LogoKind, value: string): string => {\n const encoded = encodeURIComponent(value.trim());\n return kind === \"domain\" ? encoded : `${kind}/${encoded}`;\n};\n\nconst clampSize = (size: number): number => {\n if (!Number.isFinite(size)) {\n return DEFAULT_SIZE;\n }\n return Math.min(MAX_SIZE, Math.max(MIN_SIZE, Math.round(size)));\n};\n\n/** Build an img.logo.dev URL for any lookup type. */\nexport const logoDevUrl = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): string => {\n const { kind, value } = logoSourceValue(source);\n const {\n size = DEFAULT_SIZE,\n format = \"webp\",\n theme,\n greyscale,\n retina,\n fallback,\n token,\n } = options;\n\n const params = new URLSearchParams();\n params.set(\"token\", getLogoDevToken(token));\n params.set(\"size\", String(clampSize(size)));\n params.set(\"format\", format);\n if (theme === \"light\" || theme === \"dark\") {\n params.set(\"theme\", theme);\n }\n if (greyscale) {\n params.set(\"greyscale\", \"true\");\n }\n if (retina) {\n params.set(\"retina\", \"true\");\n }\n if (fallback === \"404\") {\n params.set(\"fallback\", \"404\");\n }\n\n return `${LOGO_DEV_IMG_BASE}/${buildPath(kind, value)}?${params.toString()}`;\n};\n\n/**\n * Build `src` and a 1x/2x `srcSet` for crisp logos on high-density displays.\n * The 2x candidate uses the API's `retina` parameter, which renders at twice\n * the requested size.\n */\nexport const logoDevSrcSet = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): { src: string; srcSet: string } => {\n const src = logoDevUrl(source, { ...options, retina: false });\n const src2x = logoDevUrl(source, { ...options, retina: true });\n return { src, srcSet: `${src} 1x, ${src2x} 2x` };\n};\n\n/**\n * Append or override image parameters on an existing img.logo.dev URL, such\n * as the `logo_url` returned by the Brand Search API (which already carries\n * a token). Returns the input unchanged if it isn't a valid URL.\n */\nexport const withLogoParams = (\n logoUrl: string,\n options: Omit = {}\n): string => {\n let url: URL;\n try {\n url = new URL(logoUrl);\n } catch {\n return logoUrl;\n }\n const { size, format, theme, greyscale, retina, fallback } = options;\n if (size !== undefined) {\n url.searchParams.set(\"size\", String(clampSize(size)));\n }\n if (format) {\n url.searchParams.set(\"format\", format);\n }\n if (theme === \"light\" || theme === \"dark\") {\n url.searchParams.set(\"theme\", theme);\n }\n if (greyscale !== undefined) {\n url.searchParams.set(\"greyscale\", greyscale ? \"true\" : \"false\");\n }\n if (retina !== undefined) {\n url.searchParams.set(\"retina\", retina ? \"true\" : \"false\");\n }\n if (fallback === \"404\") {\n url.searchParams.set(\"fallback\", \"404\");\n }\n return url.toString();\n};\n\nconst WORD_SEPARATORS = /[\\s\\-_]+/;\n\n/**\n * Initials for a local fallback tile: first letters of the first two words\n * (\"Andreessen Horowitz\" → \"AH\"), or the first character for single words\n * and domains (\"stripe.com\" → \"S\").\n */\nexport const logoInitials = (label: string): string => {\n const words = label.trim().split(WORD_SEPARATORS).filter(Boolean);\n const first = words.at(0)?.at(0);\n if (!first) {\n return \"?\";\n }\n const second = words.length > 1 ? words.at(1)?.at(0) : undefined;\n return `${first}${second ?? \"\"}`.toUpperCase();\n};\n", + "content": "/**\n * Logo.dev URL builder and shared types.\n *\n * Docs: https://docs.logo.dev\n * Get a free publishable (pk_) key: https://www.logo.dev/dashboard\n */\n\nconst LOGO_DEV_IMG_BASE = \"https://img.logo.dev\";\nconst MIN_SIZE = 1;\nconst MAX_SIZE = 800;\nconst DEFAULT_SIZE = 64;\n\n/**\n * One of the five Logo.dev lookup types. A plain string is treated as a\n * domain (`\"stripe.com\"`).\n */\nexport type LogoSource =\n | { domain: string }\n | { name: string }\n | { ticker: string }\n | { crypto: string }\n | { isin: string };\n\nexport type LogoKind = \"domain\" | \"name\" | \"ticker\" | \"crypto\" | \"isin\";\n\nexport interface LogoUrlOptions {\n /**\n * What the API returns when no logo exists: `monogram` (default) serves a\n * generated letter tile with HTTP 200; `404` returns an HTTP 404 so an\n * `onError` handler can render a local fallback instead.\n */\n fallback?: \"monogram\" | \"404\";\n /** Image format. `svg` requires an Enterprise plan — never used by default. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /** Return a black-and-white version of the logo. */\n greyscale?: boolean;\n /** Render at 2× the requested size for high-density displays. */\n retina?: boolean;\n /** Logical pixel size (width and height). Clamped to 1–800. Default 64. */\n size?: number;\n /**\n * `light` inverts dark logos for light backgrounds, `dark` inverts light\n * logos for dark backgrounds. `auto` (default) requests the logo as-is.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n}\n\n/** A result row from the Logo.dev Brand Search API. */\nexport interface BrandSearchResult {\n domain: string;\n logo_url: string;\n name: string;\n}\n\nlet warnedMissingToken = false;\n\n// Declared minimally so this file typechecks in browser-only projects that\n// don't include @types/node (Vite, Deno). It stays compatible with @types/node\n// when present, and the literal `process.env.NEXT_PUBLIC_*` access below stays\n// inlinable by Next.js at build time.\ndeclare const process: { env: Record } | undefined;\n\nconst envToken = (): string | undefined =>\n typeof process === \"undefined\"\n ? // Non-Node bundlers don't define `process`; pass `token` instead.\n undefined\n : process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN;\n\n/**\n * Resolve the publishable API token: explicit argument first, then the\n * NEXT_PUBLIC_LOGO_DEV_TOKEN environment variable.\n */\nexport const getLogoDevToken = (token?: string): string => {\n const resolved = token ?? envToken() ?? \"\";\n if (!(resolved || warnedMissingToken) && typeof console !== \"undefined\") {\n warnedMissingToken = true;\n console.warn(\n \"[logo.dev] No API token found. Set NEXT_PUBLIC_LOGO_DEV_TOKEN or pass `token`. Get a free key at https://www.logo.dev/dashboard\"\n );\n }\n return resolved;\n};\n\n/** Normalize a LogoSource (or bare domain string) into its kind and value. */\nexport const logoSourceValue = (\n source: LogoSource | string\n): { kind: LogoKind; value: string } => {\n if (typeof source === \"string\") {\n return { kind: \"domain\", value: source };\n }\n if (\"domain\" in source) {\n return { kind: \"domain\", value: source.domain };\n }\n if (\"name\" in source) {\n return { kind: \"name\", value: source.name };\n }\n if (\"ticker\" in source) {\n return { kind: \"ticker\", value: source.ticker };\n }\n if (\"crypto\" in source) {\n return { kind: \"crypto\", value: source.crypto };\n }\n return { kind: \"isin\", value: source.isin };\n};\n\nconst buildPath = (kind: LogoKind, value: string): string => {\n const encoded = encodeURIComponent(value.trim());\n return kind === \"domain\" ? encoded : `${kind}/${encoded}`;\n};\n\nconst clampSize = (size: number): number => {\n if (!Number.isFinite(size)) {\n return DEFAULT_SIZE;\n }\n return Math.min(MAX_SIZE, Math.max(MIN_SIZE, Math.round(size)));\n};\n\n/** Build an img.logo.dev URL for any lookup type. */\nexport const logoDevUrl = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): string => {\n const { kind, value } = logoSourceValue(source);\n const {\n size = DEFAULT_SIZE,\n format = \"webp\",\n theme,\n greyscale,\n retina,\n fallback,\n token,\n } = options;\n\n const params = new URLSearchParams();\n params.set(\"token\", getLogoDevToken(token));\n params.set(\"size\", String(clampSize(size)));\n params.set(\"format\", format);\n if (theme === \"light\" || theme === \"dark\") {\n params.set(\"theme\", theme);\n }\n if (greyscale) {\n params.set(\"greyscale\", \"true\");\n }\n if (retina) {\n params.set(\"retina\", \"true\");\n }\n if (fallback === \"404\") {\n params.set(\"fallback\", \"404\");\n }\n\n return `${LOGO_DEV_IMG_BASE}/${buildPath(kind, value)}?${params.toString()}`;\n};\n\n/**\n * Build `src` and a 1x/2x `srcSet` for crisp logos on high-density displays.\n * The 2x candidate uses the API's `retina` parameter, which renders at twice\n * the requested size.\n */\nexport const logoDevSrcSet = (\n source: LogoSource | string,\n options: LogoUrlOptions = {}\n): { src: string; srcSet: string } => {\n const src = logoDevUrl(source, { ...options, retina: false });\n const src2x = logoDevUrl(source, { ...options, retina: true });\n return { src, srcSet: `${src} 1x, ${src2x} 2x` };\n};\n\n/**\n * Append or override image parameters on an existing img.logo.dev URL, such\n * as the `logo_url` returned by the Brand Search API (which already carries\n * a token). Returns the input unchanged if it isn't a valid URL.\n */\nexport const withLogoParams = (\n logoUrl: string,\n options: Omit = {}\n): string => {\n let url: URL;\n try {\n url = new URL(logoUrl);\n } catch {\n return logoUrl;\n }\n const { size, format, theme, greyscale, retina, fallback } = options;\n if (size !== undefined) {\n url.searchParams.set(\"size\", String(clampSize(size)));\n }\n if (format) {\n url.searchParams.set(\"format\", format);\n }\n if (theme === \"light\" || theme === \"dark\") {\n url.searchParams.set(\"theme\", theme);\n }\n if (greyscale !== undefined) {\n url.searchParams.set(\"greyscale\", greyscale ? \"true\" : \"false\");\n }\n if (retina !== undefined) {\n url.searchParams.set(\"retina\", retina ? \"true\" : \"false\");\n }\n if (fallback === \"404\") {\n url.searchParams.set(\"fallback\", \"404\");\n }\n return url.toString();\n};\n\nconst WORD_SEPARATORS = /[\\s\\-_]+/;\n\n/**\n * Initials for a local fallback tile: first letters of the first two words\n * (\"Andreessen Horowitz\" → \"AH\"), or the first character for single words\n * and domains (\"stripe.com\" → \"S\").\n */\nexport const logoInitials = (label: string): string => {\n const words = label.trim().split(WORD_SEPARATORS).filter(Boolean);\n const first = words[0]?.[0];\n if (!first) {\n return \"?\";\n }\n const second = words.length > 1 ? words[1]?.[0] : undefined;\n return `${first}${second ?? \"\"}`.toUpperCase();\n};\n", "type": "registry:lib" } ], diff --git a/r/logo.json b/r/logo.json index 86960f5..26c2ee4 100644 --- a/r/logo.json +++ b/r/logo.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/new-york/ui/logo.tsx", - "content": "\"use client\";\n\nimport {\n type ComponentProps,\n type CSSProperties,\n type ReactEventHandler,\n type ReactNode,\n useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n type LogoSource,\n type LogoUrlOptions,\n logoDevSrcSet,\n logoDevUrl,\n logoInitials,\n logoSourceValue,\n} from \"@/registry/new-york/lib/logo-dev\";\n\n/** Exactly one lookup identifier: domain, name, ticker, crypto, or isin. */\ntype LogoSourceProps =\n | {\n domain: string;\n name?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n name: string;\n domain?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n ticker: string;\n domain?: never;\n name?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n crypto: string;\n domain?: never;\n name?: never;\n ticker?: never;\n isin?: never;\n }\n | {\n isin: string;\n domain?: never;\n name?: never;\n ticker?: never;\n crypto?: never;\n };\n\ntype LogoProps = LogoSourceProps &\n Omit, \"src\" | \"srcSet\" | \"children\"> & {\n /** Logical pixel size; sets width and height so the layout never shifts. */\n size?: number;\n /** Image format. `svg` requires an Enterprise plan. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /**\n * `auto` (default) renders light and dark variants toggled by the `dark:`\n * Tailwind variant, so the right logo shows in both color schemes with\n * no theme provider. `light`/`dark` render a single variant.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Serve a 1x/2x srcSet for high-density displays. Default true. */\n retina?: boolean;\n /**\n * What renders when no logo exists: `monogram` (default) shows the\n * API-generated letter tile, `initials` shows a local initials tile, or\n * pass any React node for a custom fallback.\n */\n fallback?: \"monogram\" | \"initials\" | ReactNode;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\nconst MIN_TILE_FONT_PX = 10;\nconst TILE_FONT_RATIO = 0.4;\n\ninterface InitialsTileProps {\n className?: string;\n height: number | string;\n label: string;\n style?: CSSProperties;\n width: number | string;\n}\n\nconst InitialsTile = ({\n label,\n width,\n height,\n className,\n style,\n}: InitialsTileProps) => {\n const numericHeight = typeof height === \"number\" ? height : undefined;\n const fontSize = numericHeight\n ? Math.max(MIN_TILE_FONT_PX, Math.round(numericHeight * TILE_FONT_RATIO))\n : undefined;\n return (\n \n {logoInitials(label)}\n \n );\n};\n\nconst toLogoSource = (parts: {\n domain?: string;\n name?: string;\n ticker?: string;\n crypto?: string;\n isin?: string;\n}): LogoSource => {\n if (parts.name !== undefined) {\n return { name: parts.name };\n }\n if (parts.ticker !== undefined) {\n return { ticker: parts.ticker };\n }\n if (parts.crypto !== undefined) {\n return { crypto: parts.crypto };\n }\n if (parts.isin !== undefined) {\n return { isin: parts.isin };\n }\n return { domain: parts.domain ?? \"\" };\n};\n\n/**\n * A company logo from Logo.dev that never breaks: retina srcSet, automatic\n * dark-mode variants, and layered fallbacks (API monogram, local initials,\n * or a custom node).\n */\nfunction Logo(props: LogoProps) {\n const {\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n format,\n theme = \"auto\",\n greyscale,\n retina = true,\n fallback = \"monogram\",\n label,\n token,\n alt,\n className,\n width,\n height,\n style,\n loading = \"lazy\",\n decoding = \"async\",\n onError,\n ...imgProps\n } = props;\n\n const source = toLogoSource({ crypto, domain, isin, name, ticker });\n const { kind, value } = logoSourceValue(source);\n const labelText = label ?? name ?? value;\n\n // The error flag must reset when the logo identity changes, otherwise a\n // fallback for one company would stick around after switching to another.\n const identityKey = `${kind}:${value}:${token ?? \"\"}`;\n const [errored, setErrored] = useState(false);\n const [prevKey, setPrevKey] = useState(identityKey);\n if (prevKey !== identityKey) {\n setPrevKey(identityKey);\n setErrored(false);\n }\n\n // The API returns HTTP 200 with a generated monogram on a miss, so onError\n // only ever fires for a missing logo when the URL requests fallback=404.\n const wantsLocalFallback = fallback !== \"monogram\";\n const urlFallback: LogoUrlOptions[\"fallback\"] = wantsLocalFallback\n ? \"404\"\n : undefined;\n\n const resolvedWidth = width ?? size;\n const resolvedHeight = height ?? size;\n\n if (errored) {\n if (fallback === \"monogram\" || fallback === \"initials\") {\n return (\n \n );\n }\n return <>{fallback};\n }\n\n const sharedOptions: LogoUrlOptions = {\n fallback: urlFallback,\n format,\n greyscale,\n size,\n token,\n };\n\n const srcFor = (variant?: \"light\" | \"dark\") => {\n const options = { ...sharedOptions, theme: variant };\n if (retina) {\n return logoDevSrcSet(source, options);\n }\n return {\n src: logoDevUrl(source, { ...options, retina: false }),\n srcSet: undefined,\n };\n };\n\n const handleError: ReactEventHandler = (event) => {\n setErrored(true);\n onError?.(event);\n };\n\n const shared = {\n decoding,\n loading,\n onError: handleError,\n style,\n ...imgProps,\n };\n const resolvedAlt = alt ?? `${labelText} logo`;\n\n if (theme !== \"auto\") {\n const { src, srcSet } = srcFor(theme);\n return (\n \n );\n }\n\n const light = srcFor(\"light\");\n const dark = srcFor(\"dark\");\n return (\n <>\n \n \n \n );\n}\n\nexport type { LogoProps, LogoSourceProps };\nexport { Logo };\n", + "content": "\"use client\";\n\nimport {\n type ComponentProps,\n type CSSProperties,\n type ReactEventHandler,\n type ReactNode,\n useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n type LogoSource,\n type LogoUrlOptions,\n logoDevSrcSet,\n logoDevUrl,\n logoInitials,\n logoSourceValue,\n} from \"@/registry/new-york/lib/logo-dev\";\n\n/** Exactly one lookup identifier: domain, name, ticker, crypto, or isin. */\ntype LogoSourceProps =\n | {\n domain: string;\n name?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n name: string;\n domain?: never;\n ticker?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n ticker: string;\n domain?: never;\n name?: never;\n crypto?: never;\n isin?: never;\n }\n | {\n crypto: string;\n domain?: never;\n name?: never;\n ticker?: never;\n isin?: never;\n }\n | {\n isin: string;\n domain?: never;\n name?: never;\n ticker?: never;\n crypto?: never;\n };\n\ntype LogoProps = LogoSourceProps &\n Omit, \"src\" | \"srcSet\" | \"children\"> & {\n /** Logical pixel size; sets width and height so the layout never shifts. */\n size?: number;\n /** Image format. `svg` requires an Enterprise plan. */\n format?: \"webp\" | \"png\" | \"jpg\" | \"svg\";\n /**\n * `auto` (default) renders light and dark variants toggled by the `dark:`\n * Tailwind variant, so the right logo shows in both color schemes with\n * no theme provider. `light`/`dark` render a single variant.\n */\n theme?: \"light\" | \"dark\" | \"auto\";\n greyscale?: boolean;\n /** Serve a 1x/2x srcSet for high-density displays. Default true. */\n retina?: boolean;\n /**\n * What renders when no logo exists: `monogram` (default) shows the\n * API-generated letter tile, `initials` shows a local initials tile, or\n * pass any React node for a custom fallback.\n */\n fallback?: \"monogram\" | \"initials\" | ReactNode;\n /** Display name used for alt text and initials (e.g. \"Apple\" for ticker AAPL). */\n label?: string;\n /** Publishable (pk_) key. Defaults to NEXT_PUBLIC_LOGO_DEV_TOKEN. */\n token?: string;\n };\n\nconst MIN_TILE_FONT_PX = 10;\nconst TILE_FONT_RATIO = 0.4;\n\ninterface InitialsTileProps {\n /** Accessible name. An empty string marks the tile decorative. */\n alt: string;\n className?: string;\n height: number | string;\n label: string;\n style?: CSSProperties;\n width: number | string;\n}\n\nconst InitialsTile = ({\n alt,\n label,\n width,\n height,\n className,\n style,\n}: InitialsTileProps) => {\n const numericHeight = typeof height === \"number\" ? height : undefined;\n const fontSize = numericHeight\n ? Math.max(MIN_TILE_FONT_PX, Math.round(numericHeight * TILE_FONT_RATIO))\n : undefined;\n const decorative = alt === \"\";\n return (\n \n {logoInitials(label)}\n \n );\n};\n\nconst toLogoSource = (parts: {\n domain?: string;\n name?: string;\n ticker?: string;\n crypto?: string;\n isin?: string;\n}): LogoSource => {\n if (parts.name !== undefined) {\n return { name: parts.name };\n }\n if (parts.ticker !== undefined) {\n return { ticker: parts.ticker };\n }\n if (parts.crypto !== undefined) {\n return { crypto: parts.crypto };\n }\n if (parts.isin !== undefined) {\n return { isin: parts.isin };\n }\n return { domain: parts.domain ?? \"\" };\n};\n\n/**\n * A company logo from Logo.dev that never breaks: retina srcSet, automatic\n * dark-mode variants, and layered fallbacks (API monogram, local initials,\n * or a custom node).\n */\nfunction Logo(props: LogoProps) {\n const {\n domain,\n name,\n ticker,\n crypto,\n isin,\n size = 32,\n format,\n theme = \"auto\",\n greyscale,\n retina = true,\n fallback = \"monogram\",\n label,\n token,\n alt,\n className,\n width,\n height,\n style,\n loading = \"lazy\",\n decoding = \"async\",\n onError,\n ...imgProps\n } = props;\n\n const source = toLogoSource({ crypto, domain, isin, name, ticker });\n const { kind, value } = logoSourceValue(source);\n const labelText = label ?? name ?? value;\n const resolvedAlt = alt ?? `${labelText} logo`;\n\n // The API returns HTTP 200 with a generated monogram on a miss, so onError\n // only ever fires for a missing logo when the URL requests fallback=404.\n const wantsLocalFallback = fallback !== \"monogram\";\n const urlFallback: LogoUrlOptions[\"fallback\"] = wantsLocalFallback\n ? \"404\"\n : undefined;\n\n // Reset the error flag whenever anything that changes the requested image\n // changes (identifier, token, or any URL option), so the new image is\n // retried instead of staying stuck on a stale fallback.\n const identityKey = [\n kind,\n value,\n token ?? \"\",\n urlFallback ?? \"\",\n format ?? \"\",\n theme,\n greyscale ? \"g\" : \"\",\n size,\n ].join(\":\");\n const [errored, setErrored] = useState(false);\n const [prevKey, setPrevKey] = useState(identityKey);\n if (prevKey !== identityKey) {\n setPrevKey(identityKey);\n setErrored(false);\n }\n\n const resolvedWidth = width ?? size;\n const resolvedHeight = height ?? size;\n\n if (errored) {\n if (fallback === \"monogram\" || fallback === \"initials\") {\n return (\n \n );\n }\n return <>{fallback};\n }\n\n const sharedOptions: LogoUrlOptions = {\n fallback: urlFallback,\n format,\n greyscale,\n size,\n token,\n };\n\n const srcFor = (variant?: \"light\" | \"dark\") => {\n const options = { ...sharedOptions, theme: variant };\n if (retina) {\n return logoDevSrcSet(source, options);\n }\n return {\n src: logoDevUrl(source, { ...options, retina: false }),\n srcSet: undefined,\n };\n };\n\n const handleError: ReactEventHandler = (event) => {\n setErrored(true);\n onError?.(event);\n };\n\n const shared = {\n decoding,\n loading,\n onError: handleError,\n style,\n ...imgProps,\n };\n\n if (theme !== \"auto\") {\n const { src, srcSet } = srcFor(theme);\n return (\n \n );\n }\n\n const light = srcFor(\"light\");\n const dark = srcFor(\"dark\");\n return (\n <>\n \n \n \n );\n}\n\nexport type { LogoProps, LogoSourceProps };\nexport { Logo };\n", "type": "registry:ui" } ], diff --git a/registry/new-york/hooks/use-brand-search.ts b/registry/new-york/hooks/use-brand-search.ts index 6f91be7..bd042f1 100644 --- a/registry/new-york/hooks/use-brand-search.ts +++ b/registry/new-york/hooks/use-brand-search.ts @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import type { BrandSearchResult } from "@/registry/new-york/lib/logo-dev"; interface UseBrandSearchOptions { @@ -25,9 +25,13 @@ const performSearch = async ( limit: number, signal: AbortSignal ): Promise => { - const response = await fetch(`${endpoint}?q=${encodeURIComponent(query)}`, { - signal, - }); + // Append `q` with the right separator so a custom endpoint that already has + // query params (e.g. "/api/search?tenant=acme") stays valid. + const separator = endpoint.includes("?") ? "&" : "?"; + const response = await fetch( + `${endpoint}${separator}q=${encodeURIComponent(query)}`, + { signal } + ); if (!response.ok) { throw new Error(`Brand search failed (${response.status})`); } @@ -53,28 +57,28 @@ function useBrandSearch({ const [results, setResults] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const abortRef = useRef(null); useEffect(() => { const trimmed = query.trim(); if (!(enabled && trimmed)) { - abortRef.current?.abort(); setResults([]); setIsLoading(false); setError(null); return; } + // One controller per query. Cleanup aborts it, so an in-flight request for + // a previous query can never overwrite results for the current one, even if + // it resolves during the next debounce window. + const controller = new AbortController(); const timer = setTimeout(() => { - abortRef.current?.abort(); - const controller = new AbortController(); - abortRef.current = controller; setIsLoading(true); - performSearch(endpoint, trimmed, limit, controller.signal) .then((data) => { - setResults(data); - setError(null); + if (!controller.signal.aborted) { + setResults(data); + setError(null); + } }) .catch((caught: unknown) => { if (!isAbortError(caught)) { @@ -86,18 +90,18 @@ function useBrandSearch({ } }) .finally(() => { - // A newer request may already own the loading state. - if (abortRef.current === controller) { + if (!controller.signal.aborted) { setIsLoading(false); } }); }, debounceMs); - return () => clearTimeout(timer); + return () => { + clearTimeout(timer); + controller.abort(); + }; }, [query, enabled, endpoint, debounceMs, limit]); - useEffect(() => () => abortRef.current?.abort(), []); - return { error, isLoading, query, results, setQuery }; } diff --git a/registry/new-york/lib/logo-dev.ts b/registry/new-york/lib/logo-dev.ts index 69976e4..11fade0 100644 --- a/registry/new-york/lib/logo-dev.ts +++ b/registry/new-york/lib/logo-dev.ts @@ -56,13 +56,17 @@ export interface BrandSearchResult { let warnedMissingToken = false; -const envToken = (): string | undefined => { - if (typeof process === "undefined") { - // Non-Node bundlers (e.g. Vite) don't define `process`; pass `token` instead. - return; - } - return process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN; -}; +// Declared minimally so this file typechecks in browser-only projects that +// don't include @types/node (Vite, Deno). It stays compatible with @types/node +// when present, and the literal `process.env.NEXT_PUBLIC_*` access below stays +// inlinable by Next.js at build time. +declare const process: { env: Record } | undefined; + +const envToken = (): string | undefined => + typeof process === "undefined" + ? // Non-Node bundlers don't define `process`; pass `token` instead. + undefined + : process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN; /** * Resolve the publishable API token: explicit argument first, then the @@ -209,10 +213,10 @@ const WORD_SEPARATORS = /[\s\-_]+/; */ export const logoInitials = (label: string): string => { const words = label.trim().split(WORD_SEPARATORS).filter(Boolean); - const first = words.at(0)?.at(0); + const first = words[0]?.[0]; if (!first) { return "?"; } - const second = words.length > 1 ? words.at(1)?.at(0) : undefined; + const second = words.length > 1 ? words[1]?.[0] : undefined; return `${first}${second ?? ""}`.toUpperCase(); }; diff --git a/registry/new-york/ui/logo.tsx b/registry/new-york/ui/logo.tsx index 4793840..5694a70 100644 --- a/registry/new-york/ui/logo.tsx +++ b/registry/new-york/ui/logo.tsx @@ -86,6 +86,8 @@ const MIN_TILE_FONT_PX = 10; const TILE_FONT_RATIO = 0.4; interface InitialsTileProps { + /** Accessible name. An empty string marks the tile decorative. */ + alt: string; className?: string; height: number | string; label: string; @@ -94,6 +96,7 @@ interface InitialsTileProps { } const InitialsTile = ({ + alt, label, width, height, @@ -104,13 +107,17 @@ const InitialsTile = ({ const fontSize = numericHeight ? Math.max(MIN_TILE_FONT_PX, Math.round(numericHeight * TILE_FONT_RATIO)) : undefined; + const decorative = alt === ""; return ( @@ -175,16 +182,7 @@ function Logo(props: LogoProps) { const source = toLogoSource({ crypto, domain, isin, name, ticker }); const { kind, value } = logoSourceValue(source); const labelText = label ?? name ?? value; - - // The error flag must reset when the logo identity changes, otherwise a - // fallback for one company would stick around after switching to another. - const identityKey = `${kind}:${value}:${token ?? ""}`; - const [errored, setErrored] = useState(false); - const [prevKey, setPrevKey] = useState(identityKey); - if (prevKey !== identityKey) { - setPrevKey(identityKey); - setErrored(false); - } + const resolvedAlt = alt ?? `${labelText} logo`; // The API returns HTTP 200 with a generated monogram on a miss, so onError // only ever fires for a missing logo when the URL requests fallback=404. @@ -193,6 +191,26 @@ function Logo(props: LogoProps) { ? "404" : undefined; + // Reset the error flag whenever anything that changes the requested image + // changes (identifier, token, or any URL option), so the new image is + // retried instead of staying stuck on a stale fallback. + const identityKey = [ + kind, + value, + token ?? "", + urlFallback ?? "", + format ?? "", + theme, + greyscale ? "g" : "", + size, + ].join(":"); + const [errored, setErrored] = useState(false); + const [prevKey, setPrevKey] = useState(identityKey); + if (prevKey !== identityKey) { + setPrevKey(identityKey); + setErrored(false); + } + const resolvedWidth = width ?? size; const resolvedHeight = height ?? size; @@ -200,6 +218,7 @@ function Logo(props: LogoProps) { if (fallback === "monogram" || fallback === "initials") { return (