'use client' import { useState, useEffect, useCallback } from 'react' import { toast } from 'sonner' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Cloud, CloudOff, Check, Loader2, Upload, } from 'lucide-react' interface FolderStatus { exists: boolean hasGit: boolean hasRemote: boolean isDirty: boolean } interface BackupStatus { ghInstalled: boolean ghAuthenticated: boolean ghUser: string claude: FolderStatus codex: FolderStatus } export function BackupCard() { const [status, setStatus] = useState(null) const [loading, setLoading] = useState(false) const [syncing, setSyncing] = useState>({}) const fetchStatus = useCallback(async () => { setLoading(true) try { const res = await fetch('/api/backup') setStatus(await res.json()) } catch { // ignore } finally { setLoading(false) } }, []) useEffect(() => { fetchStatus() }, [fetchStatus]) const handleInit = async (folder: 'claude' | 'codex') => { setSyncing((s) => ({ ...s, [folder]: true })) try { const res = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'init', folder }), }) const data = await res.json() if (!res.ok) { toast.error('Backup failed', { description: data.error }) return } toast.success(`~/.${folder} backed up`, { description: ( {data.repoUrl} ), }) await fetchStatus() } catch (e) { toast.error('Backup failed', { description: (e as Error).message }) } finally { setSyncing((s) => ({ ...s, [folder]: false })) } } const handleSync = async (folder: 'claude' | 'codex') => { setSyncing((s) => ({ ...s, [folder]: true })) try { const res = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'sync', folder }), }) const data = await res.json() if (!res.ok) { toast.error('Sync failed', { description: data.error }) return } toast.success(`~/.${folder} synced`, { description: data.message }) await fetchStatus() } catch (e) { toast.error('Sync failed', { description: (e as Error).message }) } finally { setSyncing((s) => ({ ...s, [folder]: false })) } } if (loading) { return (
GitHub Backup Checking GitHub status...
Loading...
) } if (!status || !status.ghInstalled) { return (
GitHub Backup Back up your agent logs to a private GitHub repo

GitHub CLI is not installed.

Install GitHub CLI
) } if (!status.ghAuthenticated) { return (
GitHub Backup Back up your agent logs to a private GitHub repo

Not logged in to GitHub.

gh auth login
) } const folders = [ { key: 'claude' as const, label: '~/.claude', status: status.claude }, { key: 'codex' as const, label: '~/.codex', status: status.codex }, ].filter((f) => f.status.exists) return (
GitHub Backup Back up agent logs to private GitHub repos — logged in as {status.ghUser}
{folders.map(({ key, label, status: fs }) => { const isSetUp = fs.hasGit && fs.hasRemote const isSyncing = syncing[key] return (
{label} {isSetUp && !fs.isDirty && ( )} {isSetUp && fs.isDirty && ( new changes )}
{!isSetUp ? ( ) : ( )}
) })} {folders.length === 0 && (

No agent log directories found.

)}
) }