"use client"; import { useState, useCallback } from "react"; import { IconCheck, IconChevronDown, IconChevronRight, IconInfoCircle, IconX, IconRefresh, IconKey, IconTrash, IconEye, IconEyeOff, IconTerminal2, } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@kandev/ui/collapsible"; import { Input } from "@kandev/ui/input"; import { Spinner } from "@kandev/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { useAppStore } from "@/components/state-provider"; import { useGitHubStatus } from "@/hooks/domains/github/use-github-status"; import { useToast } from "@/components/toast-provider"; import { configureGitHubToken, clearGitHubToken } from "@/lib/api/domains/github-api"; import type { AuthDiagnostics } from "@/lib/types/github"; import { GitHubRateLimitDisplay } from "./github-rate-limit"; import { HostShellDialog } from "@/components/settings/host-shell-dialog"; function DiagnosticsOutput({ diagnostics }: { diagnostics: AuthDiagnostics }) { return (
{diagnostics.command} exit code: {diagnostics.exit_code}
{diagnostics.exit_code !== 0 && (

A non-zero exit code means the command failed. Review the output below for details.

)}
        {diagnostics.output.trim()}
      
); } function TokenConfigForm({ onSuccess }: { onSuccess: () => void }) { const [token, setToken] = useState(""); const [showToken, setShowToken] = useState(false); const [saving, setSaving] = useState(false); const { toast } = useToast(); const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault(); if (!token.trim()) return; setSaving(true); try { await configureGitHubToken(token.trim()); toast({ description: "GitHub token configured successfully", variant: "success" }); setToken(""); onSuccess(); } catch (err) { const message = err instanceof Error ? err.message : "Failed to configure token"; toast({ description: message, variant: "error" }); } finally { setSaving(false); } }, [token, toast, onSuccess], ); return (
setToken(e.target.value)} className="pr-8 font-mono text-sm" />

Create a{" "} Personal Access Token {" "} with repo and{" "} read:org scopes.

); } type StatusForNotConnected = { diagnostics?: AuthDiagnostics; }; function NotConnectedView({ status, refresh, ghAuthOpen, setGhAuthOpen, }: { status: StatusForNotConnected | null; refresh: () => void; ghAuthOpen: boolean; setGhAuthOpen: (open: boolean) => void; }) { // gh installed → CLI sign-in is the recommended path (browser-driven OAuth, // no PAT to manage). When it's missing we drop to PAT-only. const ghInstalled = useAppStore((state) => state.availableAgents.tools.some((t) => t.name === "gh" && t.available), ); const [tokenOpen, setTokenOpen] = useState(false); const [diagOpen, setDiagOpen] = useState(false); return (
Not connected to GitHub

Kandev also accepts a GITHUB_TOKEN environment variable on startup. The UI options below cover the common cases.

{ghInstalled ? ( setGhAuthOpen(true)} /> ) : ( )} {/* TokenConfigForm already renders the "Create a Personal Access Token with repo + read:org scopes" hint below its input. */} {status?.diagnostics && ( )}
); } function DiagnosticsDisclosure({ diagnostics, open, onOpenChange, }: { diagnostics: AuthDiagnostics; open: boolean; onOpenChange: (open: boolean) => void; }) { return ( ); } function PrimaryCLISignIn({ onClick }: { onClick: () => void }) { return (

Sign in with the GitHub CLI

Recommended. Opens a terminal that runs{" "} gh auth login with browser-based authentication.

); } function CLIUnavailableHint() { return (

The gh CLI is not installed on this host - a personal access token is the only sign-in option available here. To install it later, see{" "} cli.github.com .

); } export function GitHubStatusCard() { const { status, loaded, loading, refresh } = useGitHubStatus(); const { toast } = useToast(); const [clearing, setClearing] = useState(false); const [ghAuthOpen, setGhAuthOpen] = useState(false); const handleClearToken = useCallback(async () => { setClearing(true); try { await clearGitHubToken(); toast({ description: "GitHub token removed", variant: "success" }); refresh(); } catch { toast({ description: "Failed to clear token", variant: "error" }); } finally { setClearing(false); } }, [toast, refresh]); if (loading || !loaded) { return (
Checking GitHub connection...
); } if (!status || !status.authenticated) { return ( ); } return (
Connected as {status.username} {status.auth_method === "gh_cli" ? "gh CLI" : "PAT"} {status.token_configured && ( )}
{status.token_configured && (

Token stored in secrets. This token is used by remote agents for GitHub operations.

)}
); }