feat(claude-binary): implement robust version selector with enhanced binary detection

This commit provides a comprehensive solution to Claude binary detection issues
by implementing a user-friendly version selector UI and improving the binary
discovery logic. It addresses all concerns raised in multiple PRs and comments.

Changes:
- Add ClaudeVersionSelector component for selecting from multiple installations
- Update ClaudeBinaryDialog to use version selector instead of manual path input
- Fix unused variable warning in production builds (claude.rs:442)
- Improve select_best_installation to handle production build restrictions
- Add listClaudeInstallations API endpoint to fetch all available installations
- Make Claude version indicator clickable to navigate to Settings
- Move Claude installation selector to General tab in Settings (per user request)
- Enhance dialog UX with loading states and clear installation instructions
- Add Radix UI radio-group dependency for version selector

Fixes:
- Production build warning about unused claude_path variable
- Version detection failures in production builds due to process restrictions
- Poor UX when Claude binary is not found (now shows helpful dialog)
- Inability to easily switch between multiple Claude installations

This implementation takes inspiration from:
- PR #3: Version selector dropdown approach (preferred by users)
- PR #4: Binary detection improvements and path validation
- PR #39: Additional detection methods and error handling
- Commit 5a29f9a: Shared claude binary detection module architecture

Addresses feedback from:
- getAsterisk/claudia#4 (comment): User preference for dropdown selector
- Production build restrictions that prevent version detection
- Need for better error handling when Claude is not installed

The solution provides a seamless experience whether Claude is installed via:
- npm/yarn/bun global installation
- nvm-managed Node.js versions
- Homebrew on macOS
- System-wide installation
- Local user installation (~/.local/bin, etc.)

Refs: #3, #4, #39, 5a29f9a
This commit is contained in:
Mufeed VH
2025-06-25 02:49:24 +05:30
parent 97290e5665
commit c48a63f170
14 changed files with 556 additions and 77 deletions

View File

@@ -20,10 +20,12 @@ import { Card } from "@/components/ui/card";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import {
api,
type ClaudeSettings
type ClaudeSettings,
type ClaudeInstallation
} from "@/lib/api";
import { cn } from "@/lib/utils";
import { Toast, ToastContainer } from "@/components/ui/toast";
import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
interface SettingsProps {
/**
@@ -69,12 +71,30 @@ export const Settings: React.FC<SettingsProps> = ({
// Environment variables state
const [envVars, setEnvVars] = useState<EnvironmentVariable[]>([]);
// Claude binary path state
const [currentBinaryPath, setCurrentBinaryPath] = useState<string | null>(null);
const [selectedInstallation, setSelectedInstallation] = useState<ClaudeInstallation | null>(null);
const [binaryPathChanged, setBinaryPathChanged] = useState(false);
// Load settings on mount
useEffect(() => {
loadSettings();
loadClaudeBinaryPath();
}, []);
/**
* Loads the current Claude binary path
*/
const loadClaudeBinaryPath = async () => {
try {
const path = await api.getClaudeBinaryPath();
setCurrentBinaryPath(path);
} catch (err) {
console.error("Failed to load Claude binary path:", err);
}
};
/**
* Loads the current Claude settings
*/
@@ -159,6 +179,14 @@ export const Settings: React.FC<SettingsProps> = ({
await api.saveClaudeSettings(updatedSettings);
setSettings(updatedSettings);
// Save Claude binary path if changed
if (binaryPathChanged && selectedInstallation) {
await api.setClaudeBinaryPath(selectedInstallation.path);
setCurrentBinaryPath(selectedInstallation.path);
setBinaryPathChanged(false);
}
setToast({ message: "Settings saved successfully!", type: "success" });
} catch (err) {
console.error("Failed to save settings:", err);
@@ -246,6 +274,13 @@ export const Settings: React.FC<SettingsProps> = ({
setEnvVars(prev => prev.filter(envVar => envVar.id !== id));
};
/**
* Handle Claude installation selection
*/
const handleClaudeInstallationSelect = (installation: ClaudeInstallation) => {
setSelectedInstallation(installation);
setBinaryPathChanged(installation.path !== currentBinaryPath);
};
return (
<div className={cn("flex flex-col h-full bg-background text-foreground", className)}>
@@ -391,6 +426,25 @@ export const Settings: React.FC<SettingsProps> = ({
How long to retain chat transcripts locally (default: 30 days)
</p>
</div>
{/* Claude Binary Path Selector */}
<div className="space-y-4">
<div>
<Label className="text-sm font-medium mb-2 block">Claude Code Installation</Label>
<p className="text-xs text-muted-foreground mb-4">
Select which Claude Code installation to use
</p>
</div>
<ClaudeVersionSelector
selectedPath={currentBinaryPath}
onSelect={handleClaudeInstallationSelect}
/>
{binaryPathChanged && (
<p className="text-xs text-amber-600 dark:text-amber-400">
Claude binary path has been changed. Remember to save your settings.
</p>
)}
</div>
</div>
</div>
</Card>