import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Copy, Check, Download, ExternalLink, RefreshCw, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { useClient } from 'hooks/useClient'; import { copyToClipboard } from 'helpers/clipboard'; type RelayToken = { token: string; port: number; }; export const BrowserRelay = () => { const client = useClient(); const queryClient = useQueryClient(); const [copiedField, setCopiedField] = useState(null); const { data: status } = useQuery({ queryKey: ['browser-status'], queryFn: () => client.get<{ extensionConnected: boolean; targetCount: number }>('/browser/status'), refetchInterval: 5000, }); const { data: tokenData } = useQuery({ queryKey: ['browser-relay-token'], queryFn: () => client.get('/browser/relay-token'), }); const regenerate = useMutation({ mutationFn: () => client.post('/browser/relay-token/regenerate'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['browser-relay-token'] }); toast.success('Token regenerated — update the extension with the new token'); }, onError: () => toast.error('Failed to regenerate token'), }); const revoke = useMutation({ mutationFn: () => client.delete('/browser/relay-token'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['browser-relay-token'] }); toast.success('Token revoked'); }, onError: () => toast.error('Failed to revoke token'), }); const handleCopy = async (value: string, field: string) => { try { await copyToClipboard(value); setCopiedField(field); toast.success('Copied to clipboard'); setTimeout(() => setCopiedField(null), 2000); } catch { toast.error('Failed to copy'); } }; const isConnected = status?.extensionConnected ?? false; const serverAddress = window.location.hostname; return (
{isConnected ? (

Connected

{status?.targetCount ?? 0} tab{(status?.targetCount ?? 0) !== 1 ? 's' : ''} attached {' — '} view tabs

) : (

Connect your Chrome browser to Officer so AI agents can view and interact with your tabs.

)} {/* Step 1: Install */}

1. Install the extension

Download and unzip the extension, then load it in Chrome:

Download Extension
  1. Open{' '} chrome://extensions {' '} in Chrome
  2. Enable Developer mode (top-right toggle)
  3. Click Load unpacked and select the unzipped folder
{/* Step 2: Configure */} {tokenData && (

2. Configure the extension

Open the extension options (right-click icon → Options) and enter these values:

handleCopy(serverAddress, 'host')} /> handleCopy(String(tokenData.port), 'port')} /> handleCopy(tokenData.token, 'token')} />
)} {/* Step 3: Attach */}

3. Attach a tab

Navigate to any webpage and click the Officer extension icon in the toolbar. A cyan ON badge means the tab is connected. Then go to{' '} /browser {' '} to view your tabs.

); }; type CredentialRowProps = { label: string; value: string; masked?: boolean; copied: boolean; onCopy: () => void; }; const CredentialRow = ({ label, value, masked, copied, onCopy }: CredentialRowProps) => (
{label} {masked ? `${value.slice(0, 8)}${'•'.repeat(16)}` : value}
);