diff --git a/src/web-server/overview-routes.ts b/src/web-server/overview-routes.ts
index 9b9cdaef..208b4175 100644
--- a/src/web-server/overview-routes.ts
+++ b/src/web-server/overview-routes.ts
@@ -9,6 +9,8 @@ import * as fs from 'fs';
import * as path from 'path';
import { getCcsDir, loadConfig } from '../utils/config-manager';
import { runHealthChecks } from './health-service';
+import { getAllAuthStatus, initializeAccounts } from '../cliproxy/auth-handler';
+import { getVersion } from '../utils/version';
export const overviewRoutes = Router();
@@ -20,14 +22,25 @@ overviewRoutes.get('/', (_req: Request, res: Response) => {
const config = loadConfig();
const profileCount = Object.keys(config.profiles).length;
- const cliproxyCount = Object.keys(config.cliproxy || {}).length;
+ const cliproxyVariantCount = Object.keys(config.cliproxy || {}).length;
+
+ // Count authenticated built-in providers (gemini, codex, agy, qwen, iflow)
+ initializeAccounts();
+ const authStatuses = getAllAuthStatus();
+ const authenticatedProviderCount = authStatuses.filter((s) => s.authenticated).length;
+
+ // Total CLIProxy = custom variants + authenticated providers
+ const totalCliproxyCount = cliproxyVariantCount + authenticatedProviderCount;
// Get quick health summary
const health = runHealthChecks();
res.json({
+ version: getVersion(),
profiles: profileCount,
- cliproxy: cliproxyCount,
+ cliproxy: totalCliproxyCount,
+ cliproxyVariants: cliproxyVariantCount,
+ cliproxyProviders: authenticatedProviderCount,
accounts: getAccountCount(),
health: {
status:
@@ -38,8 +51,11 @@ overviewRoutes.get('/', (_req: Request, res: Response) => {
});
} catch {
res.json({
+ version: getVersion(),
profiles: 0,
cliproxy: 0,
+ cliproxyVariants: 0,
+ cliproxyProviders: 0,
accounts: 0,
health: { status: 'error', passed: 0, total: 0 },
});
diff --git a/ui/src/components/connection-indicator.tsx b/ui/src/components/connection-indicator.tsx
index f19ecbe6..eb00487d 100644
--- a/ui/src/components/connection-indicator.tsx
+++ b/ui/src/components/connection-indicator.tsx
@@ -11,7 +11,7 @@ export function ConnectionIndicator() {
const { status } = useWebSocket();
const statusConfig = {
- connected: { icon: Wifi, color: 'text-green-500', label: 'Connected' },
+ connected: { icon: Wifi, color: 'text-green-600', label: 'Connected' },
connecting: { icon: Wifi, color: 'text-yellow-500', label: 'Connecting...' },
disconnected: { icon: WifiOff, color: 'text-red-500', label: 'Disconnected' },
};
diff --git a/ui/src/components/health-card.tsx b/ui/src/components/health-card.tsx
index 520e2083..c3097596 100644
--- a/ui/src/components/health-card.tsx
+++ b/ui/src/components/health-card.tsx
@@ -15,7 +15,7 @@ interface HealthCheck {
const statusConfig = {
ok: {
icon: CheckCircle,
- color: 'text-green-500',
+ color: 'text-green-600',
bg: 'bg-green-50 dark:bg-green-900/20',
border: 'border-green-200 dark:border-green-800',
},
diff --git a/ui/src/components/hero-section.tsx b/ui/src/components/hero-section.tsx
new file mode 100644
index 00000000..17418826
--- /dev/null
+++ b/ui/src/components/hero-section.tsx
@@ -0,0 +1,86 @@
+import { Badge } from '@/components/ui/badge';
+import { CcsLogo } from '@/components/ccs-logo';
+import { CheckCircle2, AlertCircle, XCircle } from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+interface HeroSectionProps {
+ version?: string;
+ healthStatus?: 'ok' | 'warning' | 'error';
+ healthPassed?: number;
+ healthTotal?: number;
+}
+
+const statusConfig = {
+ ok: {
+ icon: CheckCircle2,
+ label: 'All Systems Operational',
+ color: 'text-green-600',
+ badgeBg: 'bg-green-500/10 text-green-600 border-green-500/20',
+ },
+ warning: {
+ icon: AlertCircle,
+ label: 'Some Issues Detected',
+ color: 'text-yellow-500',
+ badgeBg: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20',
+ },
+ error: {
+ icon: XCircle,
+ label: 'Action Required',
+ color: 'text-red-500',
+ badgeBg: 'bg-red-500/10 text-red-500 border-red-500/20',
+ },
+};
+
+export function HeroSection({
+ version = '5.0.0',
+ healthStatus = 'ok',
+ healthPassed = 0,
+ healthTotal = 0,
+}: HeroSectionProps) {
+ const status = statusConfig[healthStatus];
+ const StatusIcon = status.icon;
+
+ return (
+
+ {/* Subtle background pattern */}
+
+
+
+ {/* Left: Logo and Welcome */}
+
+
+
+
+
CCS Config
+
+ v{version}
+
+
+
Claude Code Switch Dashboard
+
+
+
+ {/* Right: Health Status */}
+
+
+
+
{status.label}
+ {healthTotal > 0 && (
+
+ {healthPassed}/{healthTotal} checks passed
+
+ )}
+
+
+
+
+ );
+}
diff --git a/ui/src/components/quick-commands.tsx b/ui/src/components/quick-commands.tsx
new file mode 100644
index 00000000..6ab4d6f8
--- /dev/null
+++ b/ui/src/components/quick-commands.tsx
@@ -0,0 +1,92 @@
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Copy, Check, Terminal } from 'lucide-react';
+import { useState } from 'react';
+import { cn } from '@/lib/utils';
+
+interface CommandSnippet {
+ label: string;
+ command: string;
+ description: string;
+}
+
+const defaultSnippets: CommandSnippet[] = [
+ {
+ label: 'Start Default',
+ command: 'ccs',
+ description: 'Launch Claude with default profile',
+ },
+ {
+ label: 'GLM Profile',
+ command: 'ccs glm',
+ description: 'Switch to GLM model',
+ },
+ {
+ label: 'Health Check',
+ command: 'ccs doctor',
+ description: 'Run system diagnostics',
+ },
+ {
+ label: 'Delegate Task',
+ command: 'ccs glm -p "your task"',
+ description: 'Delegate to GLM profile',
+ },
+];
+
+interface QuickCommandsProps {
+ snippets?: CommandSnippet[];
+}
+
+export function QuickCommands({ snippets = defaultSnippets }: QuickCommandsProps) {
+ const [copiedIndex, setCopiedIndex] = useState(null);
+
+ const copyToClipboard = async (text: string, index: number) => {
+ await navigator.clipboard.writeText(text);
+ setCopiedIndex(index);
+ setTimeout(() => setCopiedIndex(null), 2000);
+ };
+
+ return (
+
+
+
+
+ Quick Commands
+
+
+
+
+ {snippets.map((snippet, index) => (
+
+
+
{snippet.label}
+
+ {snippet.command}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/ui/src/components/stat-card.tsx b/ui/src/components/stat-card.tsx
index b2f6f991..8ef7f37b 100644
--- a/ui/src/components/stat-card.tsx
+++ b/ui/src/components/stat-card.tsx
@@ -1,33 +1,86 @@
import { Card, CardContent } from '@/components/ui/card';
import type { LucideIcon } from 'lucide-react';
+import { cn } from '@/lib/utils';
interface StatCardProps {
title: string;
value: number | string;
icon: LucideIcon;
color?: string;
+ variant?: 'default' | 'success' | 'warning' | 'error' | 'accent';
+ subtitle?: string;
onClick?: () => void;
}
+const variantStyles = {
+ default: {
+ iconBg: 'bg-muted',
+ iconColor: 'text-muted-foreground',
+ borderHover: 'hover:border-primary',
+ },
+ success: {
+ iconBg: 'bg-green-500/10',
+ iconColor: 'text-green-600',
+ borderHover: 'hover:border-green-500/50',
+ },
+ warning: {
+ iconBg: 'bg-yellow-500/10',
+ iconColor: 'text-yellow-500',
+ borderHover: 'hover:border-yellow-500/50',
+ },
+ error: {
+ iconBg: 'bg-red-500/10',
+ iconColor: 'text-red-500',
+ borderHover: 'hover:border-red-500/50',
+ },
+ accent: {
+ iconBg: 'bg-accent/10',
+ iconColor: 'text-accent',
+ borderHover: 'hover:border-accent/50',
+ },
+};
+
export function StatCard({
title,
value,
icon: Icon,
- color = 'text-primary',
+ color,
+ variant = 'default',
+ subtitle,
onClick,
}: StatCardProps) {
+ const styles = variantStyles[variant];
+ const iconColorClass = color || styles.iconColor;
+
return (
-
-
-
-
{title}
-
{value}
+
+
+ {/* Icon Container with background */}
+
+
+
+
+ {/* Content */}
+
+
{title}
+
{value}
+ {subtitle &&
{subtitle}
}
-
diff --git a/ui/src/hooks/use-overview.ts b/ui/src/hooks/use-overview.ts
index e395d006..09bab967 100644
--- a/ui/src/hooks/use-overview.ts
+++ b/ui/src/hooks/use-overview.ts
@@ -1,8 +1,11 @@
import { useQuery } from '@tanstack/react-query';
interface Overview {
+ version: string;
profiles: number;
cliproxy: number;
+ cliproxyVariants: number;
+ cliproxyProviders: number;
accounts: number;
health: {
status: 'ok' | 'warning' | 'error';
diff --git a/ui/src/pages/home.tsx b/ui/src/pages/home.tsx
index 2c28ca53..0e2077dc 100644
--- a/ui/src/pages/home.tsx
+++ b/ui/src/pages/home.tsx
@@ -2,6 +2,8 @@ import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { StatCard } from '@/components/stat-card';
+import { HeroSection } from '@/components/hero-section';
+import { QuickCommands } from '@/components/quick-commands';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Skeleton } from '@/components/ui/skeleton';
import {
@@ -14,14 +16,15 @@ import {
BookOpen,
FolderOpen,
AlertTriangle,
+ ArrowRight,
} from 'lucide-react';
import { useOverview } from '@/hooks/use-overview';
import { useSharedSummary } from '@/hooks/use-shared';
-const HEALTH_COLORS = {
- ok: 'text-green-500',
- warning: 'text-yellow-500',
- error: 'text-red-500',
+const HEALTH_VARIANTS = {
+ ok: 'success',
+ warning: 'warning',
+ error: 'error',
} as const;
export function HomePage() {
@@ -32,44 +35,48 @@ export function HomePage() {
if (isOverviewLoading || isSharedLoading) {
return (
-
-
-
+ {/* Hero Skeleton */}
+
+
+ {/* Stats Skeleton */}
{[1, 2, 3, 4].map((i) => (
-
-
-
-
-
-
-
-
+
+ {/* Quick Actions Skeleton */}
+
-
-
-
-
-
-
- {[1, 2, 3].map((i) => (
-
-
-
-
-
+
+ {/* Quick Commands Skeleton */}
+
+
+
+ {[1, 2, 3, 4].map((i) => (
+
))}
@@ -77,13 +84,21 @@ export function HomePage() {
);
}
+ const healthVariant = overview?.health
+ ? HEALTH_VARIANTS[overview.health.status as keyof typeof HEALTH_VARIANTS]
+ : undefined;
+
return (
-
-
Welcome to CCS Config
-
Manage your Claude Code Switch configuration
-
+ {/* Hero Section */}
+
+ {/* Configuration Warning */}
{shared?.symlinkStatus && !shared.symlinkStatus.valid && (
@@ -98,74 +113,87 @@ export function HomePage() {
title="API Profiles"
value={overview?.profiles ?? 0}
icon={Key}
+ variant="accent"
+ subtitle="Settings-based"
onClick={() => navigate('/api')}
/>
navigate('/cliproxy')}
/>
navigate('/accounts')}
/>
navigate('/health')}
/>
{/* Quick Actions */}
-
+
Quick Actions
-
+ {/* Quick Commands */}
+
+
{/* Shared Data Summary */}
-
-
- Shared Data
- navigate('/shared')}>
- View All
+
+
+
+
+ Shared Data
+
+ navigate('/shared')}
+ className="gap-1 opacity-70 group-hover:opacity-100 transition-opacity"
+ >
+ View All
-
-
-
{shared?.commands ?? 0}
+
+ {shared?.commands ?? 0}
Commands
-
-
{shared?.skills ?? 0}
+
+ {shared?.skills ?? 0}
Skills
-
-
{shared?.agents ?? 0}
+
+ {shared?.agents ?? 0}
Agents