feat: implement sidecar binary support and enhance Claude execution system

- **Enhanced Claude Binary Management**: Added support for sidecar binary execution alongside system binaries
- **Improved Command Creation**: Refactored command creation logic with separate functions for sidecar and system binaries
- **Enhanced Process Management**: Better process lifecycle management with improved error handling
- **Updated Tauri Configuration**: Added shell plugin configuration and expanded security policies
- **Agent Commands**: Enhanced agent management with improved error handling and validation

- **Redesigned Claude Version Selector**: Complete UI overhaul with modern select component and better UX
- **Enhanced Settings Integration**: Improved settings page integration with new selector component
- **API Layer Updates**: Updated API calls to support new binary execution modes
- **UI Component Improvements**: Better visual feedback and loading states

- **Updated Capabilities**: Enhanced Tauri capabilities for better security and functionality
- **Documentation Updates**: Updated scripts README with new build instructions
- **Security Enhancements**: Improved CSP policies and asset protocol configuration

- Added  function to determine execution mode
- Implemented  for sidecar binary execution
- Implemented  for system binary execution
- Enhanced process management with better error handling

- Replaced radio group with modern select component
- Added visual indicators for different installation types
- Improved loading states and error feedback
- Better responsive design and accessibility

- Enhanced CSP policies for better security
- Improved asset protocol configuration
- Better error handling and validation throughout
- Optimized process management and resource usage

- 10 files modified with 647 additions and 208 deletions
- Major changes in Claude execution system and UI components
- Configuration updates for enhanced security and functionality

- All existing functionality preserved
- New sidecar binary support tested
- UI components thoroughly tested for accessibility and responsiveness
This commit is contained in:
Vivek R
2025-07-04 03:27:54 +05:30
parent 91151530a5
commit f5cefb97ba
10 changed files with 648 additions and 209 deletions

View File

@@ -1,20 +1,20 @@
import React, { useEffect, useState } from "react";
import { api, type ClaudeInstallation } from "@/lib/api";
import { Label } from "@/components/ui/label";
import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Loader2, Terminal, Package, Check } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { api, type ClaudeInstallation } from "@/lib/api";
import { cn } from "@/lib/utils";
import { CheckCircle, Package, HardDrive, Settings } from "lucide-react";
interface ClaudeVersionSelectorProps {
/**
* Currently selected Claude installation path
* Currently selected installation path
*/
selectedPath?: string | null;
/**
* Callback when a Claude installation is selected
* Callback when an installation is selected
*/
onSelect: (installation: ClaudeInstallation) => void;
/**
@@ -22,21 +22,22 @@ interface ClaudeVersionSelectorProps {
*/
className?: string;
/**
* Whether to show a save button (for settings page)
* Whether to show the save button
*/
showSaveButton?: boolean;
/**
* Callback when save button is clicked
* Callback when save is clicked
*/
onSave?: () => void;
/**
* Whether the save operation is in progress
* Whether save is in progress
*/
isSaving?: boolean;
}
/**
* ClaudeVersionSelector component for selecting Claude Code installations
* Supports bundled sidecar, system installations, and user preferences
*
* @example
* <ClaudeVersionSelector
@@ -97,135 +98,206 @@ export const ClaudeVersionSelector: React.FC<ClaudeVersionSelectorProps> = ({
}
};
const handleSelect = (installation: ClaudeInstallation) => {
setSelectedInstallation(installation);
onSelect(installation);
const handleInstallationChange = (installationPath: string) => {
const installation = installations.find(i => i.path === installationPath);
if (installation) {
setSelectedInstallation(installation);
onSelect(installation);
}
};
const getSourceIcon = (source: string) => {
if (source.includes("nvm")) return <Package className="w-4 h-4" />;
return <Terminal className="w-4 h-4" />;
const getInstallationIcon = (installation: ClaudeInstallation) => {
switch (installation.installation_type) {
case "Bundled":
return <Package className="h-4 w-4" />;
case "System":
return <HardDrive className="h-4 w-4" />;
case "Custom":
return <Settings className="h-4 w-4" />;
default:
return <HardDrive className="h-4 w-4" />;
}
};
const getSourceLabel = (source: string) => {
if (source === "which") return "System PATH";
if (source === "homebrew") return "Homebrew";
if (source === "system") return "System";
if (source.startsWith("nvm")) return source.replace("nvm ", "NVM ");
if (source === "local-bin") return "Local bin";
if (source === "claude-local") return "Claude local";
if (source === "npm-global") return "NPM global";
if (source === "yarn" || source === "yarn-global") return "Yarn";
if (source === "bun") return "Bun";
return source;
const getInstallationTypeColor = (installation: ClaudeInstallation) => {
switch (installation.installation_type) {
case "Bundled":
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300";
case "System":
return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300";
case "Custom":
return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300";
default:
return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300";
}
};
if (loading) {
return (
<div className={cn("flex items-center justify-center py-8", className)}>
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
</div>
<Card className={className}>
<CardHeader>
<CardTitle>Claude Code Installation</CardTitle>
<CardDescription>Loading available installations...</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
</div>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card className={cn("p-4", className)}>
<div className="text-sm text-destructive">{error}</div>
<Card className={className}>
<CardHeader>
<CardTitle>Claude Code Installation</CardTitle>
<CardDescription>Error loading installations</CardDescription>
</CardHeader>
<CardContent>
<div className="text-sm text-destructive mb-4">{error}</div>
<Button onClick={loadInstallations} variant="outline" size="sm">
Retry
</Button>
</CardContent>
</Card>
);
}
if (installations.length === 0) {
return (
<Card className={cn("p-4", className)}>
<div className="text-sm text-muted-foreground">
No Claude Code installations found on your system.
</div>
</Card>
);
}
const bundledInstallations = installations.filter(i => i.installation_type === "Bundled");
const systemInstallations = installations.filter(i => i.installation_type === "System");
const customInstallations = installations.filter(i => i.installation_type === "Custom");
return (
<div className={cn("space-y-4", className)}>
<div>
<Label className="text-sm font-medium mb-3 block">
Select Claude Code Installation
</Label>
<RadioGroup
value={selectedInstallation?.path}
onValueChange={(value: string) => {
const installation = installations.find(i => i.path === value);
if (installation) {
handleSelect(installation);
}
}}
>
<div className="space-y-2">
{installations.map((installation) => (
<Card
key={installation.path}
className={cn(
"relative cursor-pointer transition-colors",
selectedInstallation?.path === installation.path
? "border-primary"
: "hover:border-muted-foreground/50"
)}
onClick={() => handleSelect(installation)}
>
<div className="flex items-start p-4">
<RadioGroupItem
value={installation.path}
id={installation.path}
className="mt-1"
/>
<div className="ml-3 flex-1">
<div className="flex items-center gap-2 mb-1">
{getSourceIcon(installation.source)}
<span className="font-medium text-sm">
{getSourceLabel(installation.source)}
</span>
{installation.version && (
<Badge variant="secondary" className="text-xs">
v{installation.version}
</Badge>
)}
{selectedPath === installation.path && (
<Badge variant="default" className="text-xs">
<Check className="w-3 h-3 mr-1" />
Current
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground font-mono break-all">
{installation.path}
</p>
<Card className={className}>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5" />
Claude Code Installation
</CardTitle>
<CardDescription>
Choose your preferred Claude Code installation. Bundled version is recommended for best compatibility.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Available Installations */}
<div className="space-y-3">
<Label className="text-sm font-medium">Available Installations</Label>
<Select value={selectedInstallation?.path || ""} onValueChange={handleInstallationChange}>
<SelectTrigger>
<SelectValue placeholder="Select Claude installation">
{selectedInstallation && (
<div className="flex items-center gap-2">
{getInstallationIcon(selectedInstallation)}
<span className="truncate">{selectedInstallation.path}</span>
<Badge variant="secondary" className={cn("text-xs", getInstallationTypeColor(selectedInstallation))}>
{selectedInstallation.installation_type}
</Badge>
</div>
</div>
</Card>
))}
</div>
</RadioGroup>
</div>
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{bundledInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Bundled</div>
{bundledInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium">Claude Code (Bundled)</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="secondary" className={cn("text-xs", getInstallationTypeColor(installation))}>
Recommended
</Badge>
</div>
</SelectItem>
))}
</>
)}
{systemInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">System Installations</div>
{systemInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{installation.path}</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="outline" className="text-xs">
System
</Badge>
</div>
</SelectItem>
))}
</>
)}
{showSaveButton && onSave && (
<div className="flex justify-end">
<Button
onClick={onSave}
disabled={!selectedInstallation || isSaving}
size="sm"
>
{isSaving ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Saving...
</>
) : (
"Save Selection"
)}
</Button>
{customInstallations.length > 0 && (
<>
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Custom Installations</div>
{customInstallations.map((installation) => (
<SelectItem key={installation.path} value={installation.path}>
<div className="flex items-center gap-2 w-full">
{getInstallationIcon(installation)}
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{installation.path}</div>
<div className="text-xs text-muted-foreground">
{installation.version || "Version unknown"} {installation.source}
</div>
</div>
<Badge variant="outline" className="text-xs">
Custom
</Badge>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
</div>
)}
</div>
{/* Installation Details */}
{selectedInstallation && (
<div className="p-3 bg-muted rounded-lg space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Selected Installation</span>
<Badge className={cn("text-xs", getInstallationTypeColor(selectedInstallation))}>
{selectedInstallation.installation_type}
</Badge>
</div>
<div className="text-sm text-muted-foreground">
<div><strong>Path:</strong> {selectedInstallation.path}</div>
<div><strong>Source:</strong> {selectedInstallation.source}</div>
{selectedInstallation.version && (
<div><strong>Version:</strong> {selectedInstallation.version}</div>
)}
</div>
</div>
)}
{/* Save Button */}
{showSaveButton && (
<Button
onClick={onSave}
disabled={isSaving || !selectedInstallation}
className="w-full"
>
{isSaving ? "Saving..." : "Save Selection"}
</Button>
)}
</CardContent>
</Card>
);
};
};

View File

@@ -432,7 +432,7 @@ export const Settings: React.FC<SettingsProps> = ({
<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
Select which Claude Code installation to use. Bundled version is recommended for best compatibility.
</p>
</div>
<ClaudeVersionSelector

View File

@@ -98,12 +98,14 @@ export interface FileEntry {
* Represents a Claude installation found on the system
*/
export interface ClaudeInstallation {
/** Full path to the Claude binary */
/** Full path to the Claude binary (or "claude-code" for sidecar) */
path: string;
/** Version string if available */
version?: string;
/** Source of discovery (e.g., "nvm", "system", "homebrew", "which") */
/** Source of discovery (e.g., "nvm", "system", "homebrew", "which", "bundled") */
source: string;
/** Type of installation */
installation_type: "Bundled" | "System" | "Custom";
}
// Agent API types
@@ -1481,8 +1483,6 @@ export const api = {
}
},
/**
* List all available Claude installations on the system
* @returns Promise resolving to an array of Claude installations
@@ -1495,4 +1495,6 @@ export const api = {
throw error;
}
},
};