{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "smithery-tokens",
	"title": "Smithery Tokens",
	"description": "A component for managing Smithery API tokens with selection and creation capabilities.",
	"dependencies": ["@smithery/api", "jotai", "lucide-react"],
	"registryDependencies": ["button", "dialog", "select"],
	"files": [
		{
			"path": "registry/new-york/smithery/tokens.tsx",
			"content": "\"use client\";\n\nimport { useAtom } from \"jotai\";\nimport { Loader2, Plus, Trash2 } from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/ai-elements/code-block\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogHeader,\n\tDialogTitle,\n\tDialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { selectedTokenAtom, tokensCreatedAtom } from \"@/hooks/use-smithery\";\nimport { useSmitheryContext } from \"@/registry/new-york/smithery/smithery-provider\";\n\nfunction formatTimeRemaining(expiresAt: string): string {\n\tif (!expiresAt || expiresAt === \"never\") return \"1 hour\";\n\tconst expiresDate = new Date(expiresAt);\n\tconst now = new Date();\n\tconst diffMs = expiresDate.getTime() - now.getTime();\n\tif (diffMs <= 0) return \"expired\";\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tif (diffMins < 60) {\n\t\treturn `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"}`;\n\t}\n\tconst diffHours = Math.floor(diffMins / 60);\n\treturn `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"}`;\n}\n\nexport function Tokens() {\n\tconst [tokensCreated, setTokensCreated] = useAtom(tokensCreatedAtom);\n\tconst [selectedToken, setSelectedToken] = useAtom(selectedTokenAtom);\n\tconst [isOpen, setIsOpen] = useState(false);\n\tconst [isCreatingToken, setIsCreatingToken] = useState(false);\n\tconst [isCreatingNamespace, setIsCreatingNamespace] = useState(false);\n\tconst [newNamespaceName, setNewNamespaceName] = useState(\"\");\n\tconst [showNamespaceInput, setShowNamespaceInput] = useState(false);\n\n\tconst {\n\t\tcreateToken,\n\t\tcreateNamespace,\n\t\tloading,\n\t\tnamespace,\n\t\tnamespaces,\n\t\tsetNamespace,\n\t\tsandboxMode,\n\t\ttokenExpiresAt,\n\t} = useSmitheryContext();\n\tconst [namespaceError, setNamespaceError] = useState<string | null>(null);\n\n\tconst handleRemoveToken = () => {\n\t\tif (!selectedToken) return;\n\n\t\tsetTokensCreated((prev) =>\n\t\t\tprev.filter((token) => token.token !== selectedToken.token),\n\t\t);\n\t\tsetSelectedToken(null);\n\t};\n\n\tconst handleCreateToken = async () => {\n\t\tsetIsCreatingToken(true);\n\t\ttry {\n\t\t\tawait createToken();\n\t\t} finally {\n\t\t\tsetIsCreatingToken(false);\n\t\t}\n\t};\n\n\tconst handleCreateNamespace = async () => {\n\t\tif (!newNamespaceName.trim()) return;\n\t\tsetIsCreatingNamespace(true);\n\t\tsetNamespaceError(null);\n\t\ttry {\n\t\t\tawait createNamespace(newNamespaceName.trim());\n\t\t\tsetNewNamespaceName(\"\");\n\t\t\tsetShowNamespaceInput(false);\n\t\t} catch (err) {\n\t\t\tsetNamespaceError(\n\t\t\t\terr instanceof Error ? err.message : \"Failed to create namespace\",\n\t\t\t);\n\t\t} finally {\n\t\t\tsetIsCreatingNamespace(false);\n\t\t}\n\t};\n\n\tif (loading) {\n\t\treturn (\n\t\t\t<div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n\t\t\t\t<Loader2 className=\"size-4 animate-spin\" />\n\t\t\t\t<span>Loading...</span>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (!selectedToken) return null;\n\n\treturn (\n\t\t<div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n\t\t\t<span>\n\t\t\t\t*****{selectedToken.token.slice(-4)}\n\t\t\t</span>\n\t\t\t{sandboxMode ? (\n\t\t\t\t<span className=\"text-xs bg-amber-100 text-amber-800 px-2 py-0.5 rounded border border-amber-200\">\n\t\t\t\t\tSandbox Mode\n\t\t\t\t</span>\n\t\t\t) : (\n\t\t\t\tnamespace && (\n\t\t\t\t\t<span className=\"text-xs bg-muted px-2 py-0.5 rounded\">\n\t\t\t\t\t\t{namespace}\n\t\t\t\t\t</span>\n\t\t\t\t)\n\t\t\t)}\n\t\t\t<Dialog open={isOpen} onOpenChange={setIsOpen}>\n\t\t\t\t<DialogTrigger asChild>\n\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\tSettings\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogTrigger>\n\t\t\t\t<DialogContent>\n\t\t\t\t\t<DialogHeader>\n\t\t\t\t\t\t<DialogTitle>Settings</DialogTitle>\n\t\t\t\t\t</DialogHeader>\n\t\t\t\t\t<div className=\"space-y-6\">\n\t\t\t\t\t\t{/* Namespace Section (or Sandbox Mode info) */}\n\t\t\t\t\t\t{sandboxMode ? (\n\t\t\t\t\t\t\t<div className=\"space-y-3 rounded-lg border border-amber-200 bg-amber-50 p-4\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs bg-amber-100 text-amber-800 px-2 py-0.5 rounded border border-amber-200 font-medium\">\n\t\t\t\t\t\t\t\t\t\tSandbox Mode\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-amber-700\">\n\t\t\t\t\t\t\t\t\t\tToken expires in {formatTimeRemaining(tokenExpiresAt)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-amber-900\">\n\t\t\t\t\t\t\t\t\tYou&apos;re using a temporary token in sandbox mode. Your\n\t\t\t\t\t\t\t\t\tconnections are private to this browser, but they will expire\n\t\t\t\t\t\t\t\t\tin 1 hour.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<div className=\"border-t border-amber-200 pt-3\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-sm text-amber-900 font-medium mb-2 block\">\n\t\t\t\t\t\t\t\t\t\tPersist Connections with a Smithery Account\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<p className=\"text-xs mb-2 text-amber-800\">\n\t\t\t\t\t\t\t\t\t\tTo save your work and use Smithery across devices, create a\n\t\t\t\t\t\t\t\t\t\tfree account and switch to Persistent Mode by running:\n\t\t\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t\t\t<CodeBlock\n\t\t\t\t\t\t\t\t\t\tcode=\"npx @smithery/cli@latest whoami --server\"\n\t\t\t\t\t\t\t\t\t\tlanguage=\"bash\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<CodeBlockCopyButton />\n\t\t\t\t\t\t\t\t\t</CodeBlock>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<div className=\"text-sm font-medium\">Namespace</div>\n\t\t\t\t\t\t\t\t{namespaces.length > 0 && (\n\t\t\t\t\t\t\t\t\t<Select value={namespace} onValueChange={setNamespace}>\n\t\t\t\t\t\t\t\t\t\t<SelectTrigger className=\"w-full\">\n\t\t\t\t\t\t\t\t\t\t\t<SelectValue placeholder=\"Select namespace\" />\n\t\t\t\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t{namespaces.map((ns) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={ns} value={ns}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{ns}\n\t\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{namespaceError && (\n\t\t\t\t\t\t\t\t\t<div className=\"text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md\">\n\t\t\t\t\t\t\t\t\t\t{namespaceError}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{showNamespaceInput ? (\n\t\t\t\t\t\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\t\tplaceholder=\"Namespace name\"\n\t\t\t\t\t\t\t\t\t\t\tvalue={newNamespaceName}\n\t\t\t\t\t\t\t\t\t\t\tonChange={(e) => setNewNamespaceName(e.target.value)}\n\t\t\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (e.key === \"Enter\") handleCreateNamespace();\n\t\t\t\t\t\t\t\t\t\t\t\tif (e.key === \"Escape\") {\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetShowNamespaceInput(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetNewNamespaceName(\"\");\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={isCreatingNamespace}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tonClick={handleCreateNamespace}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={isCreatingNamespace || !newNamespaceName.trim()}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{isCreatingNamespace ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<Loader2 className=\"size-4 animate-spin\" />\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t\"Create\"\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetShowNamespaceInput(false);\n\t\t\t\t\t\t\t\t\t\t\t\tsetNewNamespaceName(\"\");\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\tdisabled={isCreatingNamespace}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => setShowNamespaceInput(true)}\n\t\t\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Plus className=\"size-4 mr-2\" />\n\t\t\t\t\t\t\t\t\t\tCreate Namespace\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{/* Token Section */}\n\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t<div className=\"text-sm font-medium\">Token</div>\n\t\t\t\t\t\t\t{tokensCreated.length > 1 && (\n\t\t\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\t\t\tvalue={selectedToken.token}\n\t\t\t\t\t\t\t\t\tonValueChange={(tokenValue) => {\n\t\t\t\t\t\t\t\t\t\tconst token = tokensCreated.find(\n\t\t\t\t\t\t\t\t\t\t\t(t) => t.token === tokenValue,\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\tif (token) setSelectedToken(token);\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<SelectTrigger className=\"w-full\">\n\t\t\t\t\t\t\t\t\t\t<SelectValue />\n\t\t\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t{tokensCreated.map((token) => (\n\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={token.token} value={token.token}>\n\t\t\t\t\t\t\t\t\t\t\t\t*****{token.token.slice(-4)}\n\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<div className=\"rounded-lg border p-4 space-y-2\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t\t\t\t\t<div className=\"space-y-1\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t*****{selectedToken.token.slice(-4)}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t{selectedToken.expiresAt !== \"never\" && (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\tExpires:{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t{new Date(selectedToken.expiresAt).toLocaleString()}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\tsize=\"icon-sm\"\n\t\t\t\t\t\t\t\t\t\tonClick={handleRemoveToken}\n\t\t\t\t\t\t\t\t\t\taria-label=\"Remove token\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Trash2 className=\"size-4\" />\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tonClick={handleCreateToken}\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\t\tdisabled={isCreatingToken}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{isCreatingToken ? (\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<Loader2 className=\"size-4 animate-spin\" />\n\t\t\t\t\t\t\t\t\t\tCreating...\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\"Create New Token\"\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<Button onClick={() => setIsOpen(false)} className=\"w-full\">\n\t\t\t\t\t\t\tDone\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</DialogContent>\n\t\t\t</Dialog>\n\t\t</div>\n\t);\n}\n",
			"type": "registry:component",
			"target": "components/smithery/tokens.tsx"
		}
	],
	"type": "registry:block"
}
