From 4ddb6a19954227169b3d438802179ffcaf70c695 Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Tue, 22 Jul 2025 21:16:20 +0530 Subject: [PATCH] refactor: remove bundled Claude Code binary support - Remove all bundled/sidecar binary functionality - Delete build scripts for fetching and building Claude executables - Simplify binary detection to only support system installations - Update UI to remove bundled installation options - Update build configuration and documentation --- .github/workflows/build-test.yml | 2 +- README.md | 13 +- package.json | 7 - scripts/README.md | 85 ------ scripts/build-executables.js | 215 ------------- scripts/fetch-and-build.js | 337 -------------------- scripts/prepare-bundle-native.js | 145 --------- src-tauri/capabilities/default.json | 10 - src-tauri/src/claude_binary.rs | 96 ++---- src-tauri/src/commands/agents.rs | 372 +---------------------- src-tauri/src/commands/claude.rs | 271 +---------------- src-tauri/tauri.conf.json | 14 - src/components/ClaudeVersionSelector.tsx | 33 +- src/components/Settings.tsx | 2 +- src/lib/api.ts | 6 +- 15 files changed, 40 insertions(+), 1568 deletions(-) delete mode 100644 scripts/README.md delete mode 100755 scripts/build-executables.js delete mode 100755 scripts/fetch-and-build.js delete mode 100644 scripts/prepare-bundle-native.js diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ed18244..7cef9bf 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -100,7 +100,7 @@ jobs: - name: Build frontend run: bun run build - # Build Tauri application (no bundle for faster CI) + # Build Tauri application - name: Build Tauri application run: bun run tauri build --no-bundle -d env: diff --git a/README.md b/README.md index cebfef0..0b8f489 100644 --- a/README.md +++ b/README.md @@ -253,9 +253,9 @@ brew install pkg-config bun run tauri build # The built executable will be in: - # - Linux: src-tauri/target/release/bundle/ - # - macOS: src-tauri/target/release/bundle/ - # - Windows: src-tauri/target/release/bundle/ + # - Linux: src-tauri/target/release/ + # - macOS: src-tauri/target/release/ + # - Windows: src-tauri/target/release/ ``` 4. **Platform-Specific Build Options** @@ -265,11 +265,6 @@ brew install pkg-config bun run tauri build --debug ``` - **Build without bundling (creates just the executable)** - ```bash - bun run tauri build --no-bundle - ``` - **Universal Binary for macOS (Intel + Apple Silicon)** ```bash bun run tauri build --target universal-apple-darwin @@ -324,7 +319,7 @@ The build process creates several artifacts: - `.msi` installer (Windows) - `.exe` installer (Windows) -All artifacts are located in `src-tauri/target/release/bundle/`. +All artifacts are located in `src-tauri/target/release/`. ## ๐Ÿ› ๏ธ Development diff --git a/package.json b/package.json index 3a6ea24..d35ddc8 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,8 @@ "license": "AGPL-3.0", "type": "module", "scripts": { - "predev": "bun run build:executables:current", "dev": "vite", "build": "tsc && vite build", - "prebuild": "bun run build:executables:current", - "build:executables": "bun run scripts/fetch-and-build.js --version=1.0.41", - "build:executables:current": "bun run scripts/fetch-and-build.js current --version=1.0.41", - "build:executables:linux": "bun run scripts/fetch-and-build.js linux --version=1.0.41", - "build:executables:macos": "bun run scripts/fetch-and-build.js macos --version=1.0.41", - "build:executables:windows": "bun run scripts/fetch-and-build.js windows --version=1.0.41", "preview": "vite preview", "tauri": "tauri" }, diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 1554c14..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Build Scripts - -This directory contains scripts for building Claude Code executables for all supported platforms. - -## Scripts - -### `fetch-and-build.js` -Main build script that: -1. Downloads the `@anthropic-ai/claude-code` package from npm -2. Extracts and copies required files (cli.js, yoga.wasm, vendor/) -3. Builds executables for specified platforms -4. Cleans up temporary files - -**Usage:** -```bash -# Build for all platforms -bun run scripts/fetch-and-build.js - -# Build for specific platform -bun run scripts/fetch-and-build.js linux -bun run scripts/fetch-and-build.js macos -bun run scripts/fetch-and-build.js windows -bun run scripts/fetch-and-build.js current # Current platform only -``` - -### `build-executables.js` -Low-level script that builds executables from existing source files. This is called automatically by `fetch-and-build.js`. - -### `prepare-bundle-native.js` -Prepares the CLI source for bundling by embedding assets using Bun's native embedding features. - -## NPM Scripts - -The following npm scripts are available in `package.json`: - -```bash -# Build executables for all platforms -npm run build:executables - -# Build for specific platforms -npm run build:executables:current -npm run build:executables:linux -npm run build:executables:macos -npm run build:executables:windows -``` - -## Output - -All executables are created in the `src-tauri/binaries/` directory with the following naming convention: - -### Linux Executables -- `claude-code-linux-x64` - Standard Linux x64 (glibc) -- `claude-code-linux-x64-modern` - Modern CPUs (AVX2+) -- `claude-code-linux-x64-baseline` - Older CPUs (pre-2013) -- `claude-code-linux-arm64` - ARM64 Linux -- `claude-code-linux-x64-musl` - Alpine Linux (musl) -- `claude-code-linux-x64-musl-modern` - Alpine + modern CPUs -- `claude-code-linux-x64-musl-baseline` - Alpine + older CPUs -- `claude-code-linux-arm64-musl` - ARM64 Alpine - -### macOS Executables -- `claude-code-macos-x64` - Intel Mac -- `claude-code-macos-x64-modern` - Intel Mac (modern CPUs) -- `claude-code-macos-x64-baseline` - Intel Mac (older CPUs) -- `claude-code-macos-arm64` - Apple Silicon Mac - -### Windows Executables -- `claude-code-windows-x64.exe` - Windows x64 -- `claude-code-windows-x64-modern.exe` - Windows x64 (modern CPUs) -- `claude-code-windows-x64-baseline.exe` - Windows x64 (older CPUs) - -## Features - -- **Embedded Assets**: All executables include embedded yoga.wasm and ripgrep binaries -- **Optimizations**: Built with minification and sourcemaps -- **Cross-platform**: Supports all major operating systems and architectures -- **CPU Variants**: Modern variants for newer CPUs (2013+), baseline for compatibility -- **Self-contained**: No external dependencies required at runtime -- **Tauri Integration**: Automatic sidecar binary naming for seamless Tauri integration - -## Requirements - -- **Bun**: Required for building (uses Bun's native compilation features) -- **npm**: Used to download the Claude Code package -- **tar**: For extracting the package (standard on Unix systems) diff --git a/scripts/build-executables.js b/scripts/build-executables.js deleted file mode 100755 index 13b86c7..0000000 --- a/scripts/build-executables.js +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env bun - -/** - * Build script for creating single-file executables from Claude Code package - * Uses Bun's native embedding features with all optimizations enabled - * - * Output files follow Tauri sidecar triple naming convention: name-platform-architecture - * Examples: claude-code-x86_64-apple-darwin, claude-code-aarch64-unknown-linux-gnu - * - * Usage: - * bun run build-executables.js # Build all platforms - * bun run build-executables.js linux # Build Linux executables only - * bun run build-executables.js macos # Build macOS executables only - * bun run build-executables.js windows # Build Windows executables only - * bun run build-executables.js current # Build for current platform only - */ - -import { spawn } from 'child_process'; -import { mkdir, rm } from 'fs/promises'; -import { existsSync } from 'fs'; -import { join } from 'path'; - -// All supported targets with proper output names -const PLATFORMS = { - linux: [ - // Linux x64 - glibc - { target: 'bun-linux-x64', output: 'claude-code-x86_64-unknown-linux-gnu' }, - { target: 'bun-linux-x64-modern', output: 'claude-code-modern-x86_64-unknown-linux-gnu' }, - { target: 'bun-linux-x64-baseline', output: 'claude-code-baseline-x86_64-unknown-linux-gnu' }, - - // Linux ARM64 - glibc - { target: 'bun-linux-arm64', output: 'claude-code-aarch64-unknown-linux-gnu' }, - - // Linux x64 - musl (Alpine Linux, etc.) - { target: 'bun-linux-x64-musl', output: 'claude-code-x86_64-unknown-linux-musl' }, - { target: 'bun-linux-x64-musl-modern', output: 'claude-code-modern-x86_64-unknown-linux-musl' }, - { target: 'bun-linux-x64-musl-baseline', output: 'claude-code-baseline-x86_64-unknown-linux-musl' }, - - // Linux ARM64 - musl - { target: 'bun-linux-arm64-musl', output: 'claude-code-aarch64-unknown-linux-musl' } - ], - macos: [ - // macOS x64 - { target: 'bun-darwin-x64', output: 'claude-code-x86_64-apple-darwin' }, - { target: 'bun-darwin-x64-modern', output: 'claude-code-modern-x86_64-apple-darwin' }, - { target: 'bun-darwin-x64-baseline', output: 'claude-code-baseline-x86_64-apple-darwin' }, - - // macOS ARM64 (Apple Silicon) - { target: 'bun-darwin-arm64', output: 'claude-code-aarch64-apple-darwin' } - ], - windows: [ - // Windows x64 - { target: 'bun-windows-x64', output: 'claude-code-x86_64-pc-windows-msvc.exe' }, - { target: 'bun-windows-x64-modern', output: 'claude-code-modern-x86_64-pc-windows-msvc.exe' }, - { target: 'bun-windows-x64-baseline', output: 'claude-code-baseline-x86_64-pc-windows-msvc.exe' } - ] -}; - -async function runCommand(command, args) { - return new Promise((resolve, reject) => { - console.log(`Running: ${command} ${args.join(' ')}`); - const child = spawn(command, args, { stdio: 'inherit' }); - - child.on('error', reject); - child.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`Command failed with exit code ${code}`)); - } - }); - }); -} - -async function prepareBundle() { - console.log('\nPreparing bundle with native Bun embedding...'); - await runCommand('bun', ['run', 'scripts/prepare-bundle-native.js']); -} - -async function buildExecutable(target, output) { - console.log(`\nBuilding ${output}...`); - const startTime = Date.now(); - - try { - await runCommand('bun', [ - 'build', - '--compile', - '--minify', // Optimize size - '--sourcemap', // Embed sourcemap for debugging - // '--bytecode', // Commented out - experimental feature that often fails - `--target=${target}`, - './cli-native-bundled.js', - `--outfile=src-tauri/binaries/${output}` - ]); - - const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); - console.log(`โœ“ Built ${output} in ${elapsed}s`); - } catch (error) { - // If compilation fails, throw the error - throw error; - } -} - -async function cleanupBundledFile() { - const filesToClean = ['./cli-bundled.js', './cli-native-bundled.js']; - for (const file of filesToClean) { - if (existsSync(file)) { - await rm(file); - } - } - console.log('\nโœ“ Cleaned up temporary files'); -} - -async function getCurrentPlatform() { - const arch = process.arch === 'x64' ? 'x86_64' : 'aarch64'; - let targetTriple; - - if (process.platform === 'darwin') { - targetTriple = `${arch}-apple-darwin`; - } else if (process.platform === 'linux') { - targetTriple = `${arch}-unknown-linux-gnu`; - } else if (process.platform === 'win32') { - targetTriple = `${arch}-pc-windows-msvc`; - } else { - throw new Error(`Unsupported platform: ${process.platform}`); - } - - return targetTriple; -} - -async function main() { - const arg = process.argv[2]; - - // Create src-tauri/binaries directory if it doesn't exist - if (!existsSync('src-tauri/binaries')) { - await mkdir('src-tauri/binaries', { recursive: true }); - } - - let platformsToBuild = []; - - if (!arg || arg === 'all') { - // Build all platforms - platformsToBuild = [ - ...PLATFORMS.linux, - ...PLATFORMS.macos, - ...PLATFORMS.windows - ]; - } else if (arg === 'linux') { - platformsToBuild = PLATFORMS.linux; - } else if (arg === 'macos' || arg === 'darwin') { - platformsToBuild = PLATFORMS.macos; - } else if (arg === 'windows' || arg === 'win32') { - platformsToBuild = PLATFORMS.windows; - } else if (arg === 'current') { - // Build only for current platform - const currentTargetTriple = await getCurrentPlatform(); - const allPlatforms = [ - ...PLATFORMS.linux, - ...PLATFORMS.macos, - ...PLATFORMS.windows - ]; - - // Find platform by matching the target triple in the output name - const current = allPlatforms.find(p => p.output.includes(currentTargetTriple)); - if (current) { - platformsToBuild = [current]; - } else { - console.error(`Current platform ${currentTargetTriple} not found in build targets`); - process.exit(1); - } - } else { - console.error(`Unknown argument: ${arg}`); - console.error('Usage: bun run build-executables.js [all|linux|macos|windows|current]'); - process.exit(1); - } - - console.log(`Building ${platformsToBuild.length} executable(s) with full optimizations...`); - console.log('Optimizations enabled: --minify --sourcemap'); - const startTime = Date.now(); - - try { - // Prepare the bundle once with native embedding - await prepareBundle(); - - // Build executables sequentially to avoid resource conflicts - let successCount = 0; - for (const platform of platformsToBuild) { - try { - await buildExecutable(platform.target, platform.output); - successCount++; - } catch (error) { - console.error(`Failed to build ${platform.output}:`, error.message); - } - } - - const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1); - console.log(`\nโœ… Successfully built ${successCount}/${platformsToBuild.length} executables in ${totalElapsed}s`); - console.log('\nExecutables are available in the src-tauri/binaries/ directory'); - console.log('\nNotes:'); - console.log('- All executables include embedded assets (yoga.wasm, ripgrep binaries)'); - console.log('- File names follow Tauri sidecar triple naming convention (name-platform-architecture)'); - console.log('- Modern variants require CPUs from 2013+ (AVX2 support)'); - console.log('- Baseline variants support older CPUs (pre-2013)'); - console.log('- Musl variants are for Alpine Linux and similar distributions'); - console.log('- All executables are optimized with minification and sourcemaps'); - } finally { - // Clean up temporary files - await cleanupBundledFile(); - } -} - -main().catch(error => { - console.error('Build failed:', error); - process.exit(1); -}); diff --git a/scripts/fetch-and-build.js b/scripts/fetch-and-build.js deleted file mode 100755 index f1fb9a6..0000000 --- a/scripts/fetch-and-build.js +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env bun - -/** - * Fetch Claude Code package from npm and build executables for all platforms - * - * This script: - * 1. Downloads the @anthropic-ai/claude-code package from npm - * 2. Extracts it to a temporary directory - * 3. Runs the build-executables script to create binaries for all platforms - * 4. Cleans up temporary files - * - * Usage: - * bun run fetch-and-build.js [platform] [--version=X.X.X] - * - * Where platform can be: all, linux, macos, windows, current - * - * Version can be specified via: - * - CLI argument: --version=1.0.41 (defaults to 1.0.41 if not specified) - */ - -import { spawn } from 'child_process'; -import { mkdir, rm, readdir, copyFile, access } from 'fs/promises'; -import { existsSync } from 'fs'; -import { join, resolve } from 'path'; - -/** - * Execute a shell command and return a promise - * @param {string} command - The command to execute - * @param {string[]} args - Command arguments - * @param {object} options - Spawn options - * @returns {Promise} - */ -async function runCommand(command, args = [], options = {}) { - return new Promise((resolve, reject) => { - console.log(`Running: ${command} ${args.join(' ')}`); - const child = spawn(command, args, { - stdio: 'inherit', - shell: process.platform === 'win32', - ...options - }); - - child.on('error', (error) => { - console.error(`Failed to execute command: ${error.message}`); - reject(error); - }); - - child.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`Command failed with exit code ${code}`)); - } - }); - }); -} - -/** - * Check if a file or directory exists - * @param {string} path - Path to check - * @returns {Promise} - */ -async function pathExists(path) { - try { - await access(path); - return true; - } catch { - return false; - } -} - -/** - * Parse command line arguments to extract version and platform - * @param {string[]} args - Command line arguments - * @returns {object} - Parsed arguments with platform and version - */ -function parseArguments(args) { - let platform = 'all'; - let version = null; - - for (const arg of args) { - if (arg.startsWith('--version=')) { - version = arg.split('=')[1]; - } else if (!arg.startsWith('--')) { - platform = arg; - } - } - - return { platform, version }; -} - -/** - * Determine the Claude Code version to use - * @param {string|null} cliVersion - Version from CLI argument - * @returns {string} - The version to use - */ -function determineClaudeCodeVersion(cliVersion) { - const defaultVersion = '1.0.41'; - - if (cliVersion) { - console.log(`\n๐Ÿ” Using Claude Code version from CLI argument: ${cliVersion}`); - return cliVersion; - } - - console.log(`\n๐Ÿ” Using default Claude Code version: ${defaultVersion}`); - return defaultVersion; -} - -/** - * Download and extract the Claude Code package from npm - * @param {string} version - The version of the Claude Code package to download - * @returns {Promise} - Path to the extracted package directory - */ -async function fetchClaudeCodePackage(version) { - console.log(`\n๐Ÿ“ฆ Fetching @anthropic-ai/claude-code@${version} package from npm...`); - - const tempDir = resolve('./temp-claude-package'); - const packageDir = join(tempDir, 'package'); - - try { - // Clean up any existing temp directory - if (await pathExists(tempDir)) { - console.log('Cleaning up existing temp directory...'); - await rm(tempDir, { recursive: true, force: true }); - } - - // Create temp directory - await mkdir(tempDir, { recursive: true }); - - // Download the package tarball - console.log(`Downloading package tarball for version ${version}...`); - await runCommand('npm', ['pack', `@anthropic-ai/claude-code@${version}`], { - cwd: tempDir - }); - - // Find the downloaded tarball - const files = await readdir(tempDir); - const tarball = files.find(file => file.startsWith('anthropic-ai-claude-code-') && file.endsWith('.tgz')); - - if (!tarball) { - throw new Error('Failed to find downloaded tarball'); - } - - console.log(`Found tarball: ${tarball}`); - - // Extract the tarball - console.log('Extracting package...'); - await runCommand('tar', ['-xzf', tarball], { - cwd: tempDir - }); - - // Verify extraction - if (!(await pathExists(packageDir))) { - throw new Error('Package extraction failed - package directory not found'); - } - - console.log(`โœ“ Package extracted to: ${packageDir}`); - return packageDir; - - } catch (error) { - // Clean up on error - if (await pathExists(tempDir)) { - await rm(tempDir, { recursive: true, force: true }); - } - throw error; - } -} - -/** - * Copy required files from the Claude Code package to current directory - * @param {string} packageDir - Path to the extracted package directory - */ -async function copyRequiredFiles(packageDir) { - console.log('\n๐Ÿ“‹ Copying required files from Claude Code package...'); - - const filesToCopy = [ - 'cli.js', - 'yoga.wasm' - ]; - - const directoriesToCopy = [ - 'vendor' - ]; - - // Copy individual files - for (const file of filesToCopy) { - const srcPath = join(packageDir, file); - const destPath = resolve(file); - - if (await pathExists(srcPath)) { - console.log(`Copying ${file}...`); - await copyFile(srcPath, destPath); - } else { - console.warn(`Warning: ${file} not found in package`); - } - } - - // Copy directories recursively - for (const dir of directoriesToCopy) { - const srcPath = join(packageDir, dir); - const destPath = resolve(dir); - - if (await pathExists(srcPath)) { - console.log(`Copying ${dir}/ directory...`); - - // Remove existing directory if it exists - if (await pathExists(destPath)) { - await rm(destPath, { recursive: true, force: true }); - } - - // Copy directory recursively using cp command - await runCommand('cp', ['-r', srcPath, destPath]); - } else { - console.warn(`Warning: ${dir}/ directory not found in package`); - } - } - - console.log('โœ“ Required files copied successfully'); -} - -/** - * Clean up temporary files and directories - * @param {string} packageDir - Path to the package directory to clean up - */ -async function cleanup(packageDir) { - console.log('\n๐Ÿงน Cleaning up temporary files...'); - - const tempDir = resolve('./temp-claude-package'); - - try { - if (await pathExists(tempDir)) { - await rm(tempDir, { recursive: true, force: true }); - console.log('โœ“ Temporary package directory cleaned up'); - } - - // Clean up copied files that are no longer needed - const filesToCleanup = [ - './cli.js', - './cli-bundled.js', - './cli-native-bundled.js', - './yoga.wasm' - ]; - - for (const file of filesToCleanup) { - if (await pathExists(file)) { - await rm(file); - } - } - - // Clean up vendor directory - const vendorDir = './vendor'; - if (await pathExists(vendorDir)) { - await rm(vendorDir, { recursive: true, force: true }); - } - - console.log('โœ“ Cleanup completed'); - } catch (error) { - console.warn(`Warning: Cleanup failed: ${error.message}`); - } -} - -/** - * Build executables for the specified platform(s) - * @param {string} platform - Platform to build for (all, linux, macos, windows, current) - */ -async function buildExecutables(platform = 'all') { - console.log(`\n๐Ÿ”จ Building executables for platform: ${platform}`); - - // Ensure src-tauri/binaries directory exists - if (!await pathExists('./src-tauri/binaries')) { - await mkdir('./src-tauri/binaries', { recursive: true }); - } - - // Run the build-executables script - const args = platform === 'all' ? [] : [platform]; - await runCommand('bun', ['run', './scripts/build-executables.js', ...args]); -} - -/** - * Main execution function - */ -async function main() { - // Parse command line arguments - const args = process.argv.slice(2); - const { platform, version: cliVersion } = parseArguments(args); - - const validPlatforms = ['all', 'linux', 'macos', 'darwin', 'windows', 'win32', 'current']; - - if (!validPlatforms.includes(platform)) { - console.error(`Invalid platform: ${platform}`); - console.error(`Valid platforms: ${validPlatforms.join(', ')}`); - console.error('\nUsage: bun run fetch-and-build.js [platform] [--version=X.X.X]'); - console.error('Examples:'); - console.error(' bun run fetch-and-build.js'); - console.error(' bun run fetch-and-build.js linux'); - console.error(' bun run fetch-and-build.js macos --version=1.0.42'); - process.exit(1); - } - - console.log('๐Ÿš€ Starting Claude Code fetch and build process...'); - console.log(`Target platform: ${platform}`); - - const startTime = Date.now(); - let packageDir; - - try { - // Step 1: Determine version to use - const version = determineClaudeCodeVersion(cliVersion); - - // Step 2: Fetch and extract the package - packageDir = await fetchClaudeCodePackage(version); - - // Step 3: Copy required files - await copyRequiredFiles(packageDir); - - // Step 4: Build executables - await buildExecutables(platform); - - const totalTime = ((Date.now() - startTime) / 1000).toFixed(1); - console.log(`\nโœ… Build process completed successfully in ${totalTime}s`); - console.log('\n๐Ÿ“ Executables are available in the src-tauri/binaries/ directory'); - - } catch (error) { - console.error(`\nโŒ Build process failed: ${error.message}`); - process.exit(1); - } finally { - // Always clean up, even if there was an error - if (packageDir) { - await cleanup(packageDir); - } - } -} - -// Run the main function -main().catch(error => { - console.error('Unexpected error:', error); - process.exit(1); -}); diff --git a/scripts/prepare-bundle-native.js b/scripts/prepare-bundle-native.js deleted file mode 100644 index 2e8529d..0000000 --- a/scripts/prepare-bundle-native.js +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bun - -/** - * Prepare the CLI for bundling using Bun's native embedding features - * This modifies the source to use embedded files directly - */ - -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { join } from 'path'; - -// Read the original CLI file -const cliPath = './cli.js'; -let cliContent = readFileSync(cliPath, 'utf-8'); - -console.log('Preparing CLI for native Bun embedding...'); - -// 1. Build list of embedded imports based on what files actually exist -const embeddedImports = []; -const embeddedFilesMapping = []; - -// Define all possible ripgrep files -const ripgrepFiles = [ - { path: './vendor/ripgrep/arm64-darwin/rg', var: '__embeddedRgDarwinArm64' }, - { path: './vendor/ripgrep/arm64-darwin/ripgrep.node', var: '__embeddedRgNodeDarwinArm64' }, - { path: './vendor/ripgrep/arm64-linux/rg', var: '__embeddedRgLinuxArm64' }, - { path: './vendor/ripgrep/arm64-linux/ripgrep.node', var: '__embeddedRgNodeLinuxArm64' }, - { path: './vendor/ripgrep/x64-darwin/rg', var: '__embeddedRgDarwinX64' }, - { path: './vendor/ripgrep/x64-darwin/ripgrep.node', var: '__embeddedRgNodeDarwinX64' }, - { path: './vendor/ripgrep/x64-linux/rg', var: '__embeddedRgLinuxX64' }, - { path: './vendor/ripgrep/x64-linux/ripgrep.node', var: '__embeddedRgNodeLinuxX64' }, - { path: './vendor/ripgrep/x64-win32/rg.exe', var: '__embeddedRgWin32' }, - { path: './vendor/ripgrep/x64-win32/ripgrep.node', var: '__embeddedRgNodeWin32' }, -]; - -// Always include yoga.wasm -if (existsSync('./yoga.wasm')) { - embeddedImports.push('import __embeddedYogaWasm from "./yoga.wasm" with { type: "file" };'); - embeddedFilesMapping.push(" 'yoga.wasm': __embeddedYogaWasm,"); -} else { - console.error('Warning: yoga.wasm not found'); -} - -// Only import ripgrep files that exist -for (const file of ripgrepFiles) { - if (existsSync(file.path)) { - embeddedImports.push(`import ${file.var} from "${file.path}" with { type: "file" };`); - const key = file.path.replace('./', ''); - embeddedFilesMapping.push(` '${key}': ${file.var},`); - } -} - -const embeddedCode = ` -// Embedded files using Bun's native embedding -${embeddedImports.join('\n')} - -const __embeddedFiles = { -${embeddedFilesMapping.join('\n')} -}; - -`; - -// Add imports after the shebang -const shebangMatch = cliContent.match(/^#!.*\n/); -if (shebangMatch) { - cliContent = shebangMatch[0] + embeddedCode + cliContent.substring(shebangMatch[0].length); -} else { - cliContent = embeddedCode + cliContent; -} - -// 2. Replace yoga.wasm loading - handle top-level await properly -// Original: var k81=await nUA(await VP9(CP9(import.meta.url).resolve("./yoga.wasm"))); -// Since this uses top-level await, we need to preserve that structure -const yogaLoadPattern = /var k81=await nUA\(await VP9\(CP9\(import\.meta\.url\)\.resolve\("\.\/yoga\.wasm"\)\)\);/; -// Use an IIFE to handle the async loading -const yogaLoadReplacement = `var k81=await(async()=>{return await nUA(await Bun.file(__embeddedYogaWasm).arrayBuffer())})();`; - -if (yogaLoadPattern.test(cliContent)) { - cliContent = cliContent.replace(yogaLoadPattern, yogaLoadReplacement); - console.log('โœ“ Replaced yoga.wasm loading with embedded version'); -} else { - console.error('Warning: Could not find yoga.wasm loading pattern'); - // Try a more general pattern - const generalYogaPattern = /var\s+(\w+)\s*=\s*await\s+nUA\s*\(\s*await\s+VP9\s*\([^)]+\.resolve\s*\(\s*["']\.\/yoga\.wasm["']\s*\)\s*\)\s*\)/; - if (generalYogaPattern.test(cliContent)) { - cliContent = cliContent.replace(generalYogaPattern, (match, varName) => { - return `var ${varName}=await(async()=>{return await nUA(await Bun.file(__embeddedYogaWasm).arrayBuffer())})()`; - }); - console.log('โœ“ Replaced yoga.wasm loading with embedded version (general pattern)'); - } -} - -// 3. Replace ripgrep path resolution -// Add check for embedded files in the ripgrep resolver -const ripgrepPattern = /let B=Db\.resolve\(et9,"vendor","ripgrep"\);/; -const ripgrepReplacement = ` -if(process.env.CLAUDE_CODE_BUNDLED || typeof __embeddedFiles !== 'undefined'){ - const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; - const rgKey = \`vendor/ripgrep/\${platform}/rg\${process.platform === "win32" ? ".exe" : ""}\`; - if(__embeddedFiles[rgKey]) return __embeddedFiles[rgKey]; -} -let B=Db.resolve(et9,"vendor","ripgrep");`; - -if (ripgrepPattern.test(cliContent)) { - cliContent = cliContent.replace(ripgrepPattern, ripgrepReplacement); - console.log('โœ“ Added embedded file handling for ripgrep'); -} - -// 4. Replace ripgrep.node loading - handle the entire if-else structure -// Look for the complete if-else pattern where B is assigned -const ripgrepNodePattern = /if\(typeof Bun!=="undefined"&&Bun\.embeddedFiles\?\.length>0\)B="\.\/ripgrep\.node";else/; -const ripgrepNodeReplacement = `if(typeof Bun!=="undefined"&&Bun.embeddedFiles?.length>0)B=(()=>{ - const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; - const nodeKey = \`vendor/ripgrep/\${platform}/ripgrep.node\`; - return __embeddedFiles[nodeKey] || "./ripgrep.node"; -})();else`; - -if (ripgrepNodePattern.test(cliContent)) { - cliContent = cliContent.replace(ripgrepNodePattern, ripgrepNodeReplacement); - console.log('โœ“ Added embedded file handling for ripgrep.node'); -} else { - // Fallback to simpler pattern if the exact pattern doesn't match - const simplePattern = /B="\.\/ripgrep\.node"/; - if (simplePattern.test(cliContent)) { - cliContent = cliContent.replace(simplePattern, `B=(()=>{ - const platform = process.platform === "win32" ? "x64-win32" : \`\${process.arch}-\${process.platform}\`; - const nodeKey = \`vendor/ripgrep/\${platform}/ripgrep.node\`; - return __embeddedFiles[nodeKey] || "./ripgrep.node"; - })()`); - console.log('โœ“ Added embedded file handling for ripgrep.node (fallback pattern)'); - } -} - -// Set bundled mode indicator -cliContent = cliContent.replace( - /process\.env\.CLAUDE_CODE_ENTRYPOINT="cli"/, - 'process.env.CLAUDE_CODE_ENTRYPOINT="cli";process.env.CLAUDE_CODE_BUNDLED="1"' -); - -// Write the modified content -const outputPath = './cli-native-bundled.js'; -writeFileSync(outputPath, cliContent); - -console.log(`\nโœ… Created ${outputPath} ready for bundling with native embedding`); -console.log('\nNow you can run:'); -console.log(` bun build --compile --minify ./cli-native-bundled.js --outfile dist/claude-code`); diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 25f4b85..0eeb8f7 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -14,11 +14,6 @@ { "identifier": "shell:allow-execute", "allow": [ - { - "name": "claude-code", - "sidecar": true, - "args": true - }, { "name": "claude", "sidecar": false, @@ -29,11 +24,6 @@ { "identifier": "shell:allow-spawn", "allow": [ - { - "name": "claude-code", - "sidecar": true, - "args": true - }, { "name": "claude", "sidecar": false, diff --git a/src-tauri/src/claude_binary.rs b/src-tauri/src/claude_binary.rs index 36e88dc..7dc8b98 100644 --- a/src-tauri/src/claude_binary.rs +++ b/src-tauri/src/claude_binary.rs @@ -3,7 +3,7 @@ use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; /// Shared module for detecting Claude Code binary installations -/// Supports NVM installations, aliased paths, version-based selection, and bundled sidecars +/// Supports NVM installations, aliased paths, and version-based selection use std::path::PathBuf; use std::process::Command; use tauri::Manager; @@ -11,8 +11,6 @@ use tauri::Manager; /// Type of Claude installation #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum InstallationType { - /// Bundled sidecar binary (preferred) - Bundled, /// System-installed binary System, /// Custom path specified by user @@ -22,11 +20,11 @@ pub enum InstallationType { /// Represents a Claude installation with metadata #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClaudeInstallation { - /// Full path to the Claude binary (or "claude-code" for sidecar) + /// Full path to the Claude binary pub path: String, /// Version string if available pub version: Option, - /// Source of discovery (e.g., "nvm", "system", "homebrew", "which", "bundled") + /// Source of discovery (e.g., "nvm", "system", "homebrew", "which") pub source: String, /// Type of installation pub installation_type: InstallationType, @@ -50,12 +48,7 @@ pub fn find_claude_binary(app_handle: &tauri::AppHandle) -> Result Result(0), - ).unwrap_or_else(|_| "bundled".to_string()); + ).unwrap_or_else(|_| "system".to_string()); info!("User preference for Claude installation: {}", preference); - - // If user prefers bundled and it's available, use it - if preference == "bundled" && is_sidecar_available(app_handle) { - info!("Using bundled Claude Code sidecar per user preference"); - return Ok("claude-code".to_string()); - } } } } - // Check for bundled sidecar (if no preference or bundled preferred) - if is_sidecar_available(app_handle) { - info!("Found bundled Claude Code sidecar"); - return Ok("claude-code".to_string()); - } - // Discover all available system installations let installations = discover_system_installations(); @@ -113,67 +94,29 @@ pub fn find_claude_binary(app_handle: &tauri::AppHandle) -> Result bool { - // Try to create a sidecar command to test availability - use tauri_plugin_shell::ShellExt; - - match app_handle.shell().sidecar("claude-code") { - Ok(_) => { - debug!("Bundled Claude Code sidecar is available"); - true - } - Err(e) => { - debug!("Bundled Claude Code sidecar not available: {}", e); - false - } - } -} - /// Discovers all available Claude installations and returns them for selection /// This allows UI to show a version selector pub fn discover_claude_installations() -> Vec { info!("Discovering all Claude installations..."); - let mut installations = Vec::new(); + let mut installations = discover_system_installations(); - // Always add bundled sidecar as first option if available - // We can't easily check version for sidecar without spawning it, so we'll mark it as bundled - installations.push(ClaudeInstallation { - path: "claude-code".to_string(), - version: None, // Version will be determined at runtime - source: "bundled".to_string(), - installation_type: InstallationType::Bundled, - }); - - // Add system installations - installations.extend(discover_system_installations()); - - // Sort by installation type (Bundled first), then by version (highest first), then by source preference + // Sort by version (highest first), then by source preference installations.sort_by(|a, b| { - // First sort by installation type (Bundled comes first) - match (&a.installation_type, &b.installation_type) { - (InstallationType::Bundled, InstallationType::Bundled) => Ordering::Equal, - (InstallationType::Bundled, _) => Ordering::Less, - (_, InstallationType::Bundled) => Ordering::Greater, - _ => { - // For non-bundled installations, sort by version then source - match (&a.version, &b.version) { - (Some(v1), Some(v2)) => { - // Compare versions in descending order (newest first) - match compare_versions(v2, v1) { - Ordering::Equal => { - // If versions are equal, prefer by source - source_preference(a).cmp(&source_preference(b)) - } - other => other, - } + match (&a.version, &b.version) { + (Some(v1), Some(v2)) => { + // Compare versions in descending order (newest first) + match compare_versions(v2, v1) { + Ordering::Equal => { + // If versions are equal, prefer by source + source_preference(a).cmp(&source_preference(b)) } - (Some(_), None) => Ordering::Less, // Version comes before no version - (None, Some(_)) => Ordering::Greater, - (None, None) => source_preference(a).cmp(&source_preference(b)), + other => other, } } + (Some(_), None) => Ordering::Less, // Version comes before no version + (None, Some(_)) => Ordering::Greater, + (None, None) => source_preference(a).cmp(&source_preference(b)), } }); @@ -183,7 +126,6 @@ pub fn discover_claude_installations() -> Vec { /// Returns a preference score for installation sources (lower is better) fn source_preference(installation: &ClaudeInstallation) -> u8 { match installation.source.as_str() { - "bundled" => 0, // Bundled sidecar has highest preference "which" => 1, "homebrew" => 2, "system" => 3, @@ -200,7 +142,7 @@ fn source_preference(installation: &ClaudeInstallation) -> u8 { } } -/// Discovers all Claude system installations on the system (excludes bundled sidecar) +/// Discovers all Claude installations on the system fn discover_system_installations() -> Vec { let mut installations = Vec::new(); diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index 1c8d4e1..349e2bf 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -766,37 +766,8 @@ pub async fn execute_agent( "--dangerously-skip-permissions".to_string(), ]; - // Execute based on whether we should use sidecar or system binary - if should_use_sidecar(&claude_path) { - spawn_agent_sidecar(app, run_id, agent_id, agent.name.clone(), args, project_path, task, execution_model, db, registry).await - } else { - spawn_agent_system(app, run_id, agent_id, agent.name.clone(), claude_path, args, project_path, task, execution_model, db, registry).await - } -} - -/// Determines whether to use sidecar or system binary execution for agents -fn should_use_sidecar(claude_path: &str) -> bool { - claude_path == "claude-code" -} - -/// Creates a sidecar command for agent execution -fn create_agent_sidecar_command( - app: &AppHandle, - args: Vec, - project_path: &str, -) -> Result { - let mut sidecar_cmd = app - .shell() - .sidecar("claude-code") - .map_err(|e| format!("Failed to create sidecar command: {}", e))?; - - // Add all arguments - sidecar_cmd = sidecar_cmd.args(args); - - // Set working directory - sidecar_cmd = sidecar_cmd.current_dir(project_path); - - Ok(sidecar_cmd) + // Execute using system binary + spawn_agent_system(app, run_id, agent_id, agent.name.clone(), claude_path, args, project_path, task, execution_model, db, registry).await } /// Creates a system binary command for agent execution @@ -821,239 +792,6 @@ fn create_agent_system_command( } /// Spawn agent using sidecar command -async fn spawn_agent_sidecar( - app: AppHandle, - run_id: i64, - _agent_id: i64, - _agent_name: String, - args: Vec, - project_path: String, - _task: String, - _execution_model: String, - db: State<'_, AgentDb>, - registry: State<'_, crate::process::ProcessRegistryState>, -) -> Result { - use std::sync::Mutex; - - // Create the sidecar command - let sidecar_cmd = create_agent_sidecar_command(&app, args, &project_path)?; - - // Spawn the sidecar process - let (mut rx, child) = sidecar_cmd - .spawn() - .map_err(|e| format!("Failed to spawn Claude sidecar: {}", e))?; - - // Get the child PID for logging - let pid = child.pid(); - info!("โœ… Spawned Claude sidecar process with PID: {:?}", pid); - - // Update the database with PID and status - let now = chrono::Utc::now().to_rfc3339(); - { - let conn = db.0.lock().map_err(|e| e.to_string())?; - conn.execute( - "UPDATE agent_runs SET status = 'running', pid = ?1, process_started_at = ?2 WHERE id = ?3", - params![pid as i64, now, run_id], - ).map_err(|e| e.to_string())?; - info!("๐Ÿ“ Updated database with running status and PID"); - } - - // We'll extract the session ID from Claude's init message - let session_id_holder: Arc>> = Arc::new(Mutex::new(None)); - - // Create variables we need for the spawned task - let app_dir = app - .path() - .app_data_dir() - .expect("Failed to get app data dir"); - let db_path = app_dir.join("agents.db"); - let db_path_for_stream = db_path.clone(); // Clone for the streaming task - - // Spawn task to read events from sidecar - let app_handle = app.clone(); - let session_id_holder_clone = session_id_holder.clone(); - let live_output = std::sync::Arc::new(Mutex::new(String::new())); - let live_output_clone = live_output.clone(); - let registry_clone = registry.0.clone(); - let first_output = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); - let first_output_clone = first_output.clone(); - - let sidecar_task = tokio::spawn(async move { - info!("๐Ÿ“– Starting to read Claude sidecar events..."); - let mut line_count = 0; - - while let Some(event) = rx.recv().await { - match event { - tauri_plugin_shell::process::CommandEvent::Stdout(data) => { - let line = String::from_utf8_lossy(&data).trim().to_string(); - if !line.is_empty() { - line_count += 1; - - // Log first output - if !first_output_clone.load(std::sync::atomic::Ordering::Relaxed) { - info!("๐ŸŽ‰ First output received from Claude sidecar! Line: {}", line); - first_output_clone.store(true, std::sync::atomic::Ordering::Relaxed); - } - - if line_count <= 5 { - info!("sidecar stdout[{}]: {}", line_count, line); - } else { - debug!("sidecar stdout[{}]: {}", line_count, line); - } - - // Store live output - if let Ok(mut output) = live_output_clone.lock() { - output.push_str(&line); - output.push('\n'); - } - - // Also store in process registry for cross-session access - let _ = registry_clone.append_live_output(run_id, &line); - - // Extract session ID from JSONL output - if let Ok(json) = serde_json::from_str::(&line) { - // Claude Code uses "session_id" (underscore), not "sessionId" - if json.get("type").and_then(|t| t.as_str()) == Some("system") && - json.get("subtype").and_then(|s| s.as_str()) == Some("init") { - if let Some(sid) = json.get("session_id").and_then(|s| s.as_str()) { - if let Ok(mut current_session_id) = session_id_holder_clone.lock() { - if current_session_id.is_none() { - *current_session_id = Some(sid.to_string()); - info!("๐Ÿ”‘ Extracted session ID: {}", sid); - - // Update database immediately with session ID - if let Ok(conn) = Connection::open(&db_path_for_stream) { - match conn.execute( - "UPDATE agent_runs SET session_id = ?1 WHERE id = ?2", - params![sid, run_id], - ) { - Ok(rows) => { - if rows > 0 { - info!("โœ… Updated agent run {} with session ID immediately", run_id); - } - } - Err(e) => { - error!("โŒ Failed to update session ID immediately: {}", e); - } - } - } - } - } - } - } - } - - // Emit the line to the frontend with run_id for isolation - let _ = app_handle.emit(&format!("agent-output:{}", run_id), &line); - // Also emit to the generic event for backward compatibility - let _ = app_handle.emit("agent-output", &line); - } - } - tauri_plugin_shell::process::CommandEvent::Stderr(data) => { - let line = String::from_utf8_lossy(&data).trim().to_string(); - if !line.is_empty() { - error!("sidecar stderr: {}", line); - // Emit error lines to the frontend with run_id for isolation - let _ = app_handle.emit(&format!("agent-error:{}", run_id), &line); - // Also emit to the generic event for backward compatibility - let _ = app_handle.emit("agent-error", &line); - } - } - tauri_plugin_shell::process::CommandEvent::Terminated { .. } => { - info!("๐Ÿ“– Claude sidecar process terminated"); - break; - } - tauri_plugin_shell::process::CommandEvent::Error(e) => { - error!("๐Ÿ”ฅ Claude sidecar error: {}", e); - break; - } - _ => { - // Handle any other event types we might not know about - debug!("Received unknown sidecar event type"); - } - } - } - - info!("๐Ÿ“– Finished reading Claude sidecar events. Total lines: {}", line_count); - }); - - // Monitor process status and wait for completion - tokio::spawn(async move { - info!("๐Ÿ• Starting sidecar process monitoring..."); - - // Wait for first output with timeout - for i in 0..300 { - // 30 seconds (300 * 100ms) - if first_output.load(std::sync::atomic::Ordering::Relaxed) { - info!("โœ… Output detected after {}ms, continuing normal execution", i * 100); - break; - } - - if i == 299 { - warn!("โฐ TIMEOUT: No output from Claude sidecar after 30 seconds"); - warn!("๐Ÿ’ก This usually means:"); - warn!(" 1. Claude sidecar is waiting for user input"); - warn!(" 2. Authentication issues (API key not found/invalid)"); - warn!(" 3. Network connectivity issues"); - warn!(" 4. Claude failed to initialize but didn't report an error"); - - // Update database with failed status - if let Ok(conn) = Connection::open(&db_path) { - let _ = conn.execute( - "UPDATE agent_runs SET status = 'failed', completed_at = CURRENT_TIMESTAMP WHERE id = ?1", - params![run_id], - ); - } - - let _ = app.emit("agent-complete", false); - let _ = app.emit(&format!("agent-complete:{}", run_id), false); - return; - } - - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - } - - // Wait for sidecar task to complete - info!("โณ Waiting for sidecar reading to complete..."); - let _ = sidecar_task.await; - - // Get the session ID that was extracted - let extracted_session_id = if let Ok(Some(sid)) = session_id_holder.lock().map(|s| s.clone()) { - sid - } else { - String::new() - }; - - // Update the run record with session ID and mark as completed - if let Ok(conn) = Connection::open(&db_path) { - info!("๐Ÿ”„ Updating database with extracted session ID: {}", extracted_session_id); - match conn.execute( - "UPDATE agent_runs SET session_id = ?1, status = 'completed', completed_at = CURRENT_TIMESTAMP WHERE id = ?2", - params![extracted_session_id, run_id], - ) { - Ok(rows_affected) => { - if rows_affected > 0 { - info!("โœ… Successfully updated agent run {} with session ID: {}", run_id, extracted_session_id); - } else { - warn!("โš ๏ธ No rows affected when updating agent run {} with session ID", run_id); - } - } - Err(e) => { - error!("โŒ Failed to update agent run {} with session ID: {}", run_id, e); - } - } - } else { - error!("โŒ Failed to open database to update session ID for run {}", run_id); - } - - info!("โœ… Claude sidecar execution monitoring complete"); - - let _ = app.emit("agent-complete", true); - let _ = app.emit(&format!("agent-complete:{}", run_id), true); - }); - - Ok(run_id) -} /// Spawn agent using system binary command async fn spawn_agent_system( @@ -1830,20 +1568,7 @@ pub async fn get_claude_binary_path(db: State<'_, AgentDb>) -> Result, path: String) -> Result<(), String> { let conn = db.0.lock().map_err(|e| e.to_string())?; - // Special handling for bundled sidecar reference - if path == "claude-code" { - // For bundled sidecar, we don't need to validate file existence - // as it's handled by Tauri's sidecar system - conn.execute( - "INSERT INTO app_settings (key, value) VALUES ('claude_binary_path', ?1) - ON CONFLICT(key) DO UPDATE SET value = ?1", - params![path], - ) - .map_err(|e| format!("Failed to save Claude binary path: {}", e))?; - return Ok(()); - } - - // Validate that the path exists and is executable for system installations + // Validate that the path exists and is executable let path_buf = std::path::PathBuf::from(&path); if !path_buf.exists() { return Err(format!("File does not exist: {}", path)); @@ -1883,77 +1608,6 @@ pub async fn list_claude_installations( return Err("No Claude Code installations found on the system".to_string()); } - // For bundled installations, execute the sidecar to get the actual version - for installation in &mut installations { - if installation.installation_type == crate::claude_binary::InstallationType::Bundled { - // Try to get the version by executing the sidecar - use tauri_plugin_shell::process::CommandEvent; - - // Create a temporary directory for the sidecar to run in - let temp_dir = std::env::temp_dir(); - - // Create sidecar command with --version flag - let sidecar_cmd = match app - .shell() - .sidecar("claude-code") { - Ok(cmd) => cmd.args(["--version"]).current_dir(&temp_dir), - Err(e) => { - log::warn!("Failed to create sidecar command for version check: {}", e); - continue; - } - }; - - // Spawn the sidecar and collect output - match sidecar_cmd.spawn() { - Ok((mut rx, _child)) => { - let mut stdout_output = String::new(); - let mut stderr_output = String::new(); - - // Set a timeout for version check - let timeout = tokio::time::Duration::from_secs(5); - let start_time = tokio::time::Instant::now(); - - while let Ok(Some(event)) = tokio::time::timeout_at( - start_time + timeout, - rx.recv() - ).await { - match event { - CommandEvent::Stdout(data) => { - stdout_output.push_str(&String::from_utf8_lossy(&data)); - } - CommandEvent::Stderr(data) => { - stderr_output.push_str(&String::from_utf8_lossy(&data)); - } - CommandEvent::Terminated { .. } => { - break; - } - CommandEvent::Error(e) => { - log::warn!("Error during sidecar version check: {}", e); - break; - } - _ => {} - } - } - - // Use regex to directly extract version pattern - let version_regex = regex::Regex::new(r"(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)").ok(); - - if let Some(regex) = version_regex { - if let Some(captures) = regex.captures(&stdout_output) { - if let Some(version_match) = captures.get(1) { - installation.version = Some(version_match.as_str().to_string()); - log::info!("Bundled sidecar version: {}", version_match.as_str()); - } - } - } - } - Err(e) => { - log::warn!("Failed to spawn sidecar for version check: {}", e); - } - } - } - } - Ok(installations) } @@ -2012,26 +1666,6 @@ fn create_command_with_env(program: &str) -> Command { tokio_cmd.env("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"); } - // BEGIN PATCH: Ensure bundled sidecar directory is in PATH when using the "claude-code" placeholder - if program == "claude-code" { - // Attempt to locate the sidecar binaries directory that Tauri uses during development - // At compile-time, CARGO_MANIFEST_DIR resolves to the absolute path of the src-tauri crate. - // The sidecar binaries live in /binaries. - #[allow(clippy::redundant_clone)] - let sidecar_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("binaries"); - if sidecar_dir.exists() { - if let Some(sidecar_dir_str) = sidecar_dir.to_str() { - let current_path = std::env::var("PATH").unwrap_or_default(); - let separator = if cfg!(target_os = "windows") { ";" } else { ":" }; - if !current_path.split(separator).any(|p| p == sidecar_dir_str) { - let new_path = format!("{}{}{}", sidecar_dir_str, separator, current_path); - tokio_cmd.env("PATH", new_path); - } - } - } - } - // END PATCH - tokio_cmd } diff --git a/src-tauri/src/commands/claude.rs b/src-tauri/src/commands/claude.rs index 1b4df05..8b96480 100644 --- a/src-tauri/src/commands/claude.rs +++ b/src-tauri/src/commands/claude.rs @@ -266,30 +266,6 @@ fn create_command_with_env(program: &str) -> Command { tokio_cmd } -/// Determines whether to use sidecar or system binary execution -fn should_use_sidecar(claude_path: &str) -> bool { - claude_path == "claude-code" -} - -/// Creates a sidecar command with the given arguments -fn create_sidecar_command( - app: &AppHandle, - args: Vec, - project_path: &str, -) -> Result { - let mut sidecar_cmd = app - .shell() - .sidecar("claude-code") - .map_err(|e| format!("Failed to create sidecar command: {}", e))?; - - // Add all arguments - sidecar_cmd = sidecar_cmd.args(args); - - // Set working directory - sidecar_cmd = sidecar_cmd.current_dir(project_path); - - Ok(sidecar_cmd) -} /// Creates a system binary command with the given arguments fn create_system_command( @@ -578,91 +554,6 @@ pub async fn check_claude_version(app: AppHandle) -> Result cmd.args(["--version"]).current_dir(&temp_dir), - Err(e) => { - log::error!("Failed to create sidecar command: {}", e); - return Ok(ClaudeVersionStatus { - is_installed: true, // We know it exists, just couldn't create command - version: None, - output: format!("Using bundled Claude Code sidecar (command creation failed: {})", e), - }); - } - }; - - // Spawn the sidecar and collect output - match sidecar_cmd.spawn() { - Ok((mut rx, _child)) => { - let mut stdout_output = String::new(); - let mut stderr_output = String::new(); - let mut exit_success = false; - - // Collect output from the sidecar - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Stdout(data) => { - let line = String::from_utf8_lossy(&data); - stdout_output.push_str(&line); - } - CommandEvent::Stderr(data) => { - let line = String::from_utf8_lossy(&data); - stderr_output.push_str(&line); - } - CommandEvent::Terminated(payload) => { - exit_success = payload.code.unwrap_or(-1) == 0; - break; - } - _ => {} - } - } - - // Use regex to directly extract version pattern (e.g., "1.0.41") - let version_regex = regex::Regex::new(r"(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)").ok(); - - let version = if let Some(regex) = version_regex { - regex.captures(&stdout_output) - .and_then(|captures| captures.get(1)) - .map(|m| m.as_str().to_string()) - } else { - None - }; - - let full_output = if stderr_output.is_empty() { - stdout_output.clone() - } else { - format!("{}\n{}", stdout_output, stderr_output) - }; - - // Check if the output matches the expected format - let is_valid = stdout_output.contains("(Claude Code)") || stdout_output.contains("Claude Code") || version.is_some(); - - return Ok(ClaudeVersionStatus { - is_installed: is_valid && exit_success, - version, - output: full_output.trim().to_string(), - }); - } - Err(e) => { - log::error!("Failed to execute sidecar: {}", e); - return Ok(ClaudeVersionStatus { - is_installed: true, // We know it exists, just couldn't get version - version: None, - output: format!("Using bundled Claude Code sidecar (version check failed: {})", e), - }); - } - } - } - use log::debug;debug!("Claude path: {}", claude_path); // In production builds, we can't check the version directly @@ -951,12 +842,8 @@ pub async fn execute_claude_code( "--dangerously-skip-permissions".to_string(), ]; - if should_use_sidecar(&claude_path) { - spawn_claude_sidecar(app, args, prompt, model, project_path).await - } else { - let cmd = create_system_command(&claude_path, args, &project_path); - spawn_claude_process(app, cmd, prompt, model, project_path).await - } + let cmd = create_system_command(&claude_path, args, &project_path); + spawn_claude_process(app, cmd, prompt, model, project_path).await } /// Continue an existing Claude Code conversation with streaming output @@ -987,12 +874,8 @@ pub async fn continue_claude_code( "--dangerously-skip-permissions".to_string(), ]; - if should_use_sidecar(&claude_path) { - spawn_claude_sidecar(app, args, prompt, model, project_path).await - } else { - let cmd = create_system_command(&claude_path, args, &project_path); - spawn_claude_process(app, cmd, prompt, model, project_path).await - } + let cmd = create_system_command(&claude_path, args, &project_path); + spawn_claude_process(app, cmd, prompt, model, project_path).await } /// Resume an existing Claude Code session by ID with streaming output @@ -1026,12 +909,8 @@ pub async fn resume_claude_code( "--dangerously-skip-permissions".to_string(), ]; - if should_use_sidecar(&claude_path) { - spawn_claude_sidecar(app, args, prompt, model, project_path).await - } else { - let cmd = create_system_command(&claude_path, args, &project_path); - spawn_claude_process(app, cmd, prompt, model, project_path).await - } + let cmd = create_system_command(&claude_path, args, &project_path); + spawn_claude_process(app, cmd, prompt, model, project_path).await } /// Cancel the currently running Claude Code execution @@ -1348,144 +1227,6 @@ async fn spawn_claude_process(app: AppHandle, mut cmd: Command, prompt: String, Ok(()) } -/// Helper function to spawn Claude sidecar process and handle streaming -async fn spawn_claude_sidecar( - app: AppHandle, - args: Vec, - prompt: String, - model: String, - project_path: String, -) -> Result<(), String> { - use std::sync::Mutex; - - // Create the sidecar command - let sidecar_cmd = create_sidecar_command(&app, args, &project_path)?; - - // Spawn the sidecar process - let (mut rx, child) = sidecar_cmd - .spawn() - .map_err(|e| format!("Failed to spawn Claude sidecar: {}", e))?; - - // Get the child PID for logging - let pid = child.pid(); - log::info!("Spawned Claude sidecar process with PID: {:?}", pid); - - // We'll extract the session ID from Claude's init message - let session_id_holder: Arc>> = Arc::new(Mutex::new(None)); - let run_id_holder: Arc>> = Arc::new(Mutex::new(None)); - - // Register with ProcessRegistry - let registry = app.state::(); - let registry_clone = registry.0.clone(); - let project_path_clone = project_path.clone(); - let prompt_clone = prompt.clone(); - let model_clone = model.clone(); - - // Spawn task to read events from sidecar - let app_handle = app.clone(); - let session_id_holder_clone = session_id_holder.clone(); - let run_id_holder_clone = run_id_holder.clone(); - - tauri::async_runtime::spawn(async move { - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Stdout(line_bytes) => { - let line = String::from_utf8_lossy(&line_bytes); - let line_str = line.trim_end_matches('\n').trim_end_matches('\r'); - - if !line_str.is_empty() { - log::debug!("Claude sidecar stdout: {}", line_str); - - // Parse the line to check for init message with session ID - if let Ok(msg) = serde_json::from_str::(line_str) { - if msg["type"] == "system" && msg["subtype"] == "init" { - if let Some(claude_session_id) = msg["session_id"].as_str() { - let mut session_id_guard = session_id_holder_clone.lock().unwrap(); - if session_id_guard.is_none() { - *session_id_guard = Some(claude_session_id.to_string()); - log::info!("Extracted Claude session ID: {}", claude_session_id); - - // Register with ProcessRegistry using Claude's session ID - match registry_clone.register_claude_session( - claude_session_id.to_string(), - pid, - project_path_clone.clone(), - prompt_clone.clone(), - model_clone.clone(), - ) { - Ok(run_id) => { - log::info!("Registered Claude sidecar session with run_id: {}", run_id); - let mut run_id_guard = run_id_holder_clone.lock().unwrap(); - *run_id_guard = Some(run_id); - } - Err(e) => { - log::error!("Failed to register Claude sidecar session: {}", e); - } - } - } - } - } - } - - // Store live output in registry if we have a run_id - if let Some(run_id) = *run_id_holder_clone.lock().unwrap() { - let _ = registry_clone.append_live_output(run_id, line_str); - } - - // Emit the line to the frontend with session isolation if we have session ID - if let Some(ref session_id) = *session_id_holder_clone.lock().unwrap() { - let _ = app_handle.emit(&format!("claude-output:{}", session_id), line_str); - } - // Also emit to the generic event for backward compatibility - let _ = app_handle.emit("claude-output", line_str); - } - } - CommandEvent::Stderr(line_bytes) => { - let line = String::from_utf8_lossy(&line_bytes); - let line_str = line.trim_end_matches('\n').trim_end_matches('\r'); - - if !line_str.is_empty() { - log::error!("Claude sidecar stderr: {}", line_str); - - // Emit error lines to the frontend with session isolation if we have session ID - if let Some(ref session_id) = *session_id_holder_clone.lock().unwrap() { - let _ = app_handle.emit(&format!("claude-error:{}", session_id), line_str); - } - // Also emit to the generic event for backward compatibility - let _ = app_handle.emit("claude-error", line_str); - } - } - CommandEvent::Terminated(payload) => { - log::info!("Claude sidecar process terminated with payload: {:?}", payload); - - // Add a small delay to ensure all messages are processed - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - - let success = payload.code.unwrap_or(-1) == 0; - - if let Some(ref session_id) = *session_id_holder_clone.lock().unwrap() { - let _ = app_handle.emit(&format!("claude-complete:{}", session_id), success); - } - // Also emit to the generic event for backward compatibility - let _ = app_handle.emit("claude-complete", success); - - // Unregister from ProcessRegistry if we have a run_id - if let Some(run_id) = *run_id_holder_clone.lock().unwrap() { - let _ = registry_clone.unregister_process(run_id); - } - - break; - } - _ => { - // Handle other event types if needed - log::debug!("Claude sidecar event: {:?}", event); - } - } - } - }); - - Ok(()) -} /// Lists files and directories in a given path #[tauri::command] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 860c55c..a9876c9 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -47,19 +47,5 @@ "shell": { "open": true } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.png" - ], - "externalBin": [ - "binaries/claude-code" - ] } } diff --git a/src/components/ClaudeVersionSelector.tsx b/src/components/ClaudeVersionSelector.tsx index de8c360..763c092 100644 --- a/src/components/ClaudeVersionSelector.tsx +++ b/src/components/ClaudeVersionSelector.tsx @@ -6,7 +6,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com 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"; +import { CheckCircle, HardDrive, Settings } from "lucide-react"; interface ClaudeVersionSelectorProps { /** @@ -37,7 +37,7 @@ interface ClaudeVersionSelectorProps { /** * ClaudeVersionSelector component for selecting Claude Code installations - * Supports bundled sidecar, system installations, and user preferences + * Supports system installations and user preferences * * @example * = ({ const getInstallationIcon = (installation: ClaudeInstallation) => { switch (installation.installation_type) { - case "Bundled": - return ; case "System": return ; case "Custom": @@ -121,8 +119,6 @@ export const ClaudeVersionSelector: React.FC = ({ 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": @@ -165,7 +161,6 @@ export const ClaudeVersionSelector: React.FC = ({ ); } - 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"); @@ -177,7 +172,7 @@ export const ClaudeVersionSelector: React.FC = ({ Claude Code Installation - Choose your preferred Claude Code installation. Bundled version is recommended for best compatibility. + Choose your preferred Claude Code installation. @@ -199,28 +194,6 @@ export const ClaudeVersionSelector: React.FC = ({ - {bundledInstallations.length > 0 && ( - <> -
Bundled
- {bundledInstallations.map((installation) => ( - -
- {getInstallationIcon(installation)} -
-
Claude Code (Bundled)
-
- {installation.version || "Version unknown"} โ€ข {installation.source} -
-
- - Recommended - -
-
- ))} - - )} - {systemInstallations.length > 0 && ( <>
System Installations
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 638feb2..10e677c 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -429,7 +429,7 @@ export const Settings: React.FC = ({

- Select which Claude Code installation to use. Bundled version is recommended for best compatibility. + Select which Claude Code installation to use.