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 - Commit5a29f9a
: 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:
@@ -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>
|
||||
|
Reference in New Issue
Block a user