From a283f942a9712f97c5789ea39a508da1d5305a79 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 7 Dec 2025 20:32:00 -0500 Subject: [PATCH] feat(cliproxy): add authentication status display to web dashboard - Add auth status endpoint to show built-in profiles authentication state - Display auth status in UI with visual indicators for authenticated state - Show last authentication date and token file count - Update CLI command to better differentiate between built-in profiles and custom variants - Improve UI layout to separate built-in profiles from custom variants --- src/commands/cliproxy-command.ts | 73 ++++++++++++++++++-------------- src/web-server/routes.ts | 21 +++++++++ ui/src/hooks/use-cliproxy.ts | 7 +++ ui/src/lib/api-client.ts | 9 ++++ ui/src/pages/cliproxy.tsx | 70 ++++++++++++++++++++++++++---- 5 files changed, 141 insertions(+), 39 deletions(-) diff --git a/src/commands/cliproxy-command.ts b/src/commands/cliproxy-command.ts index 0ed84ff4..49ade110 100644 --- a/src/commands/cliproxy-command.ts +++ b/src/commands/cliproxy-command.ts @@ -23,6 +23,7 @@ import { isCLIProxyInstalled, getCLIProxyPath, } from '../cliproxy'; +import { getAllAuthStatus, getOAuthConfig } from '../cliproxy/auth-handler'; import { CLIPROXY_FALLBACK_VERSION } from '../cliproxy/platform-detector'; import { CLIPROXY_PROFILES, CLIProxyProfileName } from '../auth/profile-detector'; import { getCcsDir, getConfigPath, loadConfig } from '../utils/config-manager'; @@ -354,50 +355,60 @@ async function handleCreate(args: string[]): Promise { async function handleList(): Promise { await initUI(); - console.log(header('CLIProxy Variants')); + console.log(header('CLIProxy Profiles')); console.log(''); try { + // Show auth status for built-in profiles + console.log(subheader('Built-in Profiles')); + const authStatuses = getAllAuthStatus(); + + for (const status of authStatuses) { + const oauthConfig = getOAuthConfig(status.provider); + const icon = status.authenticated ? ok('') : warn(''); + const authLabel = status.authenticated ? color('authenticated', 'success') : dim('not authenticated'); + const lastAuthStr = status.lastAuth + ? dim(` (${status.lastAuth.toLocaleDateString()})`) + : ''; + + console.log(` ${icon} ${color(status.provider, 'command').padEnd(18)} ${oauthConfig.displayName.padEnd(16)} ${authLabel}${lastAuthStr}`); + } + console.log(''); + console.log(dim(' To authenticate: ccs --auth')); + console.log(dim(' To logout: ccs --logout')); + console.log(''); + + // Show custom variants if any const config = loadConfig(); const variants = config.cliproxy || {}; const variantNames = Object.keys(variants); - if (variantNames.length === 0) { - console.log(warn('No CLIProxy variants configured')); + if (variantNames.length > 0) { + console.log(subheader('Custom Variants')); + + // Build table data + const rows: string[][] = variantNames.map((name) => { + const variant = variants[name] as { provider: string; settings: string }; + return [name, variant.provider, variant.settings]; + }); + + // Print table + console.log( + table(rows, { + head: ['Variant', 'Provider', 'Settings'], + colWidths: [15, 12, 35], + }) + ); console.log(''); - console.log('Built-in CLIProxy profiles:'); - CLIPROXY_PROFILES.forEach((p) => console.log(` ${color(p, 'command')}`)); + console.log(dim(`Total: ${variantNames.length} custom variant(s)`)); console.log(''); - console.log('To create a custom variant:'); - console.log(` ${color('ccs cliproxy create', 'command')}`); - console.log(''); - return; } - // Build table data - const rows: string[][] = variantNames.map((name) => { - const variant = variants[name] as { provider: string; settings: string }; - return [name, variant.provider, variant.settings]; - }); - - // Print table - console.log( - table(rows, { - head: ['Variant', 'Provider', 'Settings'], - colWidths: [15, 12, 35], - }) - ); - console.log(''); - - // Show built-in profiles - console.log(subheader('Built-in Profiles')); - console.log(` ${CLIPROXY_PROFILES.join(', ')}`); - console.log(''); - - console.log(dim(`Total: ${variantNames.length} custom variant(s)`)); + console.log(dim('To create a custom variant:')); + console.log(` ${color('ccs cliproxy create', 'command')}`); console.log(''); } catch (error) { - console.log(fail(`Failed to list variants: ${(error as Error).message}`)); + console.log(fail(`Failed to list profiles: ${(error as Error).message}`)); process.exit(1); } } diff --git a/src/web-server/routes.ts b/src/web-server/routes.ts index 72d93001..1e2fc971 100644 --- a/src/web-server/routes.ts +++ b/src/web-server/routes.ts @@ -11,6 +11,7 @@ import { getCcsDir, getConfigPath, loadConfig, loadSettings } from '../utils/con import { Config, Settings } from '../types/config'; import { expandPath } from '../utils/helpers'; import { runHealthChecks, fixHealthIssue } from './health-service'; +import { getAllAuthStatus, getOAuthConfig } from '../cliproxy/auth-handler'; export const apiRoutes = Router(); @@ -293,6 +294,26 @@ apiRoutes.delete('/cliproxy/:name', (req: Request, res: Response): void => { res.json({ name, deleted: true }); }); +/** + * GET /api/cliproxy/auth - Get auth status for built-in CLIProxy profiles + */ +apiRoutes.get('/cliproxy/auth', (_req: Request, res: Response) => { + const statuses = getAllAuthStatus(); + + const authStatus = statuses.map((status) => { + const oauthConfig = getOAuthConfig(status.provider); + return { + provider: status.provider, + displayName: oauthConfig.displayName, + authenticated: status.authenticated, + lastAuth: status.lastAuth?.toISOString() || null, + tokenFiles: status.tokenFiles.length, + }; + }); + + res.json({ authStatus }); +}); + // ==================== Settings (Phase 05) ==================== /** diff --git a/ui/src/hooks/use-cliproxy.ts b/ui/src/hooks/use-cliproxy.ts index c984fdb1..492463a0 100644 --- a/ui/src/hooks/use-cliproxy.ts +++ b/ui/src/hooks/use-cliproxy.ts @@ -14,6 +14,13 @@ export function useCliproxy() { }); } +export function useCliproxyAuth() { + return useQuery({ + queryKey: ['cliproxy-auth'], + queryFn: () => api.cliproxy.auth(), + }); +} + export function useCreateVariant() { const queryClient = useQueryClient(); diff --git a/ui/src/lib/api-client.ts b/ui/src/lib/api-client.ts index 1dbef42f..b9399847 100644 --- a/ui/src/lib/api-client.ts +++ b/ui/src/lib/api-client.ts @@ -51,6 +51,14 @@ export interface CreateVariant { model?: string; } +export interface AuthStatus { + provider: string; + displayName: string; + authenticated: boolean; + lastAuth: string | null; + tokenFiles: number; +} + export interface Account { name: string; type?: string; @@ -76,6 +84,7 @@ export const api = { }, cliproxy: { list: () => request<{ variants: Variant[] }>('/cliproxy'), + auth: () => request<{ authStatus: AuthStatus[] }>('/cliproxy/auth'), create: (data: CreateVariant) => request('/cliproxy', { method: 'POST', diff --git a/ui/src/pages/cliproxy.tsx b/ui/src/pages/cliproxy.tsx index 8c878efb..ffc1c376 100644 --- a/ui/src/pages/cliproxy.tsx +++ b/ui/src/pages/cliproxy.tsx @@ -5,17 +5,18 @@ import { useState } from 'react'; import { Button } from '@/components/ui/button'; -import { Plus } from 'lucide-react'; +import { Plus, Check, X } from 'lucide-react'; import { CliproxyTable } from '@/components/cliproxy-table'; import { CliproxyDialog } from '@/components/cliproxy-dialog'; -import { useCliproxy } from '@/hooks/use-cliproxy'; +import { useCliproxy, useCliproxyAuth } from '@/hooks/use-cliproxy'; export function CliproxyPage() { const [dialogOpen, setDialogOpen] = useState(false); const { data, isLoading } = useCliproxy(); + const { data: authData, isLoading: authLoading } = useCliproxyAuth(); return ( -
+

CLIProxy

@@ -29,11 +30,64 @@ export function CliproxyPage() {
- {isLoading ? ( -
Loading variants...
- ) : ( - - )} + {/* Built-in Profiles Auth Status */} +
+

Built-in Profiles

+ {authLoading ? ( +
Loading auth status...
+ ) : ( +
+ {authData?.authStatus.map((status) => ( +
+
+ {status.authenticated ? ( + + ) : ( + + )} + {status.displayName} +
+
+ {status.authenticated ? ( + <> + Authenticated + {status.lastAuth && ( + + ({new Date(status.lastAuth).toLocaleDateString()}) + + )} + + ) : ( + <> + Not authenticated + + Run: ccs {status.provider} --auth + + + )} +
+
+ ))} +
+ )} +
+ + {/* Custom Variants */} +
+

Custom Variants

+ {isLoading ? ( +
Loading variants...
+ ) : ( + + )} +
setDialogOpen(false)} />