diff --git a/src/cliproxy/binary-manager.ts b/src/cliproxy/binary-manager.ts index bdc6df0e..87ecbc28 100644 --- a/src/cliproxy/binary-manager.ts +++ b/src/cliproxy/binary-manager.ts @@ -927,7 +927,7 @@ export function getInstalledCliproxyVersion(): string { * Install a specific version of CLIProxyAPI * Deletes existing binary and downloads the specified version * - * @param version Version to install (e.g., "6.5.40") + * @param version Version to install (e.g., "6.5.53") * @param verbose Enable verbose logging */ export async function installCliproxyVersion(version: string, verbose = false): Promise { @@ -949,7 +949,7 @@ export async function installCliproxyVersion(version: string, verbose = false): /** * Fetch the latest CLIProxyAPI version from GitHub API - * @returns Latest version string (e.g., "6.5.40") + * @returns Latest version string (e.g., "6.5.53") */ export async function fetchLatestCliproxyVersion(): Promise { const manager = new BinaryManager(); diff --git a/src/cliproxy/platform-detector.ts b/src/cliproxy/platform-detector.ts index 13ef8f9b..b3f45984 100644 --- a/src/cliproxy/platform-detector.ts +++ b/src/cliproxy/platform-detector.ts @@ -11,7 +11,7 @@ import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './ty * CLIProxyAPI fallback version (used when GitHub API unavailable) * Auto-update fetches latest from GitHub; this is only a safety net */ -export const CLIPROXY_FALLBACK_VERSION = '6.5.40'; +export const CLIPROXY_FALLBACK_VERSION = '6.5.53'; /** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */ export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION; diff --git a/src/commands/cliproxy-command.ts b/src/commands/cliproxy-command.ts index fb564a34..0ed84ff4 100644 --- a/src/commands/cliproxy-command.ts +++ b/src/commands/cliproxy-command.ts @@ -633,7 +633,7 @@ async function showStatus(verbose: boolean): Promise { async function installVersion(version: string, verbose: boolean): Promise { // Validate version format (basic semver check) if (!/^\d+\.\d+\.\d+$/.test(version)) { - console.error('[X] Invalid version format. Expected format: X.Y.Z (e.g., 6.5.40)'); + console.error('[X] Invalid version format. Expected format: X.Y.Z (e.g., 6.5.53)'); process.exit(1); } @@ -732,7 +732,7 @@ export async function handleCliproxyCommand(args: string[]): Promise { if (!version || version.startsWith('-')) { console.error('[X] Missing version argument for --install'); console.error(' Usage: ccs cliproxy --install '); - console.error(' Example: ccs cliproxy --install 6.5.40'); + console.error(' Example: ccs cliproxy --install 6.5.53'); process.exit(1); } await installVersion(version, verbose); diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 2e4bf2b4..d2663ffc 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -210,7 +210,7 @@ Claude Code Profile & Model Switcher`.trim(); printSubSection('CLI Proxy Management', [ ['ccs cliproxy', 'Show CLIProxyAPI status and version'], ['ccs cliproxy --help', 'Full CLIProxy management help'], - ['ccs cliproxy --install ', 'Install specific version (e.g., 6.5.40)'], + ['ccs cliproxy --install ', 'Install specific version (e.g., 6.5.53)'], ['ccs cliproxy --latest', 'Update to latest version'], ]); diff --git a/src/web-server/shared-routes.ts b/src/web-server/shared-routes.ts index cd4adb5d..7a11fd63 100644 --- a/src/web-server/shared-routes.ts +++ b/src/web-server/shared-routes.ts @@ -127,22 +127,34 @@ function checkSymlinkStatus(): { valid: boolean; message: string } { return { valid: false, message: 'Shared directory not found' }; } - // Check if ~/.claude/commands links to shared - const claudeDir = path.join(os.homedir(), '.claude'); - const commandsLink = path.join(claudeDir, 'commands'); + // Check all three symlinks: commands, skills, agents + const linkTypes = ['commands', 'skills', 'agents']; + let validLinks = 0; - try { - if (fs.existsSync(commandsLink)) { - const stats = fs.lstatSync(commandsLink); - if (stats.isSymbolicLink()) { - const target = fs.readlinkSync(commandsLink); - if (target.includes('.ccs/shared/commands')) { - return { valid: true, message: 'Symlinks active' }; + for (const linkType of linkTypes) { + const linkPath = path.join(sharedDir, linkType); + + try { + if (fs.existsSync(linkPath)) { + const stats = fs.lstatSync(linkPath); + if (stats.isSymbolicLink()) { + const target = fs.readlinkSync(linkPath); + // Check if it points to ~/.claude/{linkType} + const expectedTarget = path.join(os.homedir(), '.claude', linkType); + if (path.resolve(path.dirname(linkPath), target) === path.resolve(expectedTarget)) { + validLinks++; + } } } + } catch { + // Not a symlink or read error } - } catch { - // Not a symlink or read error + } + + if (validLinks === linkTypes.length) { + return { valid: true, message: 'Symlinks active' }; + } else if (validLinks > 0) { + return { valid: false, message: `Symlinks partially configured (${validLinks}/${linkTypes.length})` }; } return { valid: false, message: 'Symlinks not configured' }; diff --git a/ui/src/components/ui/alert.tsx b/ui/src/components/ui/alert.tsx new file mode 100644 index 00000000..3d9bc910 --- /dev/null +++ b/ui/src/components/ui/alert.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + warning: + 'border-yellow-200 bg-yellow-50 text-yellow-900 dark:border-yellow-900/50 dark:bg-yellow-900/20 dark:text-yellow-200 [&>svg]:text-yellow-600 dark:[&>svg]:text-yellow-500', + info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900/50 dark:bg-blue-900/20 dark:text-blue-200 [&>svg]:text-blue-600 dark:[&>svg]:text-blue-500', + success: + 'border-green-200 bg-green-50 text-green-900 dark:border-green-900/50 dark:bg-green-900/20 dark:text-green-200 [&>svg]:text-green-600 dark:[&>svg]:text-green-500', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = 'Alert'; + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +AlertTitle.displayName = 'AlertTitle'; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = 'AlertDescription'; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/ui/src/components/ui/skeleton.tsx b/ui/src/components/ui/skeleton.tsx index 02535dc4..91f29da1 100644 --- a/ui/src/components/ui/skeleton.tsx +++ b/ui/src/components/ui/skeleton.tsx @@ -4,7 +4,12 @@ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) { return (
); diff --git a/ui/src/index.css b/ui/src/index.css index 7edb4d78..6cae9b14 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -99,6 +99,15 @@ --radius-lg: calc(var(--radius) + 4px); } +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} + @layer base { * { @apply border-border; diff --git a/ui/src/pages/home.tsx b/ui/src/pages/home.tsx index 012dfc70..fd34fa3f 100644 --- a/ui/src/pages/home.tsx +++ b/ui/src/pages/home.tsx @@ -2,20 +2,80 @@ 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 { Key, Zap, Users, Activity, Plus, Stethoscope, BookOpen, FolderOpen } from 'lucide-react'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Key, + Zap, + Users, + Activity, + Plus, + Stethoscope, + BookOpen, + FolderOpen, + AlertTriangle, +} 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', +} as const; + export function HomePage() { const navigate = useNavigate(); - const { data: overview } = useOverview(); - const { data: shared } = useSharedSummary(); + const { data: overview, isLoading: isOverviewLoading } = useOverview(); + const { data: shared, isLoading: isSharedLoading } = useSharedSummary(); - const healthColor = { - ok: 'text-green-500', - warning: 'text-yellow-500', - error: 'text-red-500', - }; + if (isOverviewLoading || isSharedLoading) { + return ( +
+
+ + +
+
+ {[1, 2, 3, 4].map((i) => ( +
+
+ + +
+
+ + +
+
+ ))} +
+
+ +
+ + + +
+
+
+
+ + +
+
+ {[1, 2, 3].map((i) => ( +
+ + + +
+ ))} +
+
+
+ ); + } return (
@@ -24,6 +84,14 @@ export function HomePage() {

Manage your Claude Code Switch configuration

+ {shared?.symlinkStatus && !shared.symlinkStatus.valid && ( + + + Configuration Required + {shared.symlinkStatus.message} + + )} + {/* Stats Grid */}
navigate('/health')} />
@@ -97,13 +167,6 @@ export function HomePage() { Agents
- {shared?.symlinkStatus && ( -

- {shared.symlinkStatus.message} -

- )}
diff --git a/ui/src/pages/shared.tsx b/ui/src/pages/shared.tsx index 4e4713de..a6596391 100644 --- a/ui/src/pages/shared.tsx +++ b/ui/src/pages/shared.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { useSharedItems, useSharedSummary } from '@/hooks/use-shared'; import { FileText, Sparkles, Bot, AlertTriangle } from 'lucide-react'; @@ -27,14 +28,13 @@ export function SharedPage() { {summary && !summary.symlinkStatus.valid && ( - - - - - {summary.symlinkStatus.message}. Run `ccs sync` to configure. - - - + + + Configuration Required + + {summary.symlinkStatus.message}. Run `ccs sync` to configure. + + )} {/* Tab buttons */}