refactor: install Python CLI into isolated venv instead of uv tool

Replace `uv tool install` (which installs globally into ~/.local/bin) with
a dedicated venv at an OS-specific cache directory (~/.cache/codeflash/venv
on Linux, ~/Library/Caches/codeflash/venv on macOS, %LOCALAPPDATA%\codeflash\venv
on Windows). The CLI entry point now invokes the binary directly from the venv
instead of via `uv tool run`. Also strips VIRTUAL_ENV/CONDA env vars from child
processes to avoid interference from activated environments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ali 2026-03-26 17:33:56 +02:00
parent d5f82bb48b
commit 56403d21aa
No known key found for this signature in database
GPG key ID: 44F9B42770617B9B
3 changed files with 147 additions and 120 deletions

View file

@ -4,69 +4,52 @@
* Codeflash CLI Entry Point * Codeflash CLI Entry Point
* *
* This script is the main entry point for the codeflash CLI when installed via npm. * This script is the main entry point for the codeflash CLI when installed via npm.
* It delegates to the Python codeflash CLI installed via uv. * It delegates to the Python codeflash CLI installed in a dedicated venv.
* *
* Usage: * Usage:
* npx codeflash --help * npx codeflash --help
* npx codeflash optimize --file src/utils.ts * npx codeflash optimize --file src/utils.ts
*/ */
const { spawn, spawnSync } = require('child_process'); const { spawn } = require('child_process');
const os = require('os');
const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { getCodeflashBin } = require('../scripts/paths');
/** /**
* Find the uv binary * Find the codeflash binary in the dedicated venv
*/ */
function findUv() { function findCodeflash() {
const homeDir = os.homedir(); const codeflashBin = getCodeflashBin();
const platform = os.platform(); if (fs.existsSync(codeflashBin)) {
return codeflashBin;
// Check the default uv installation location first
const uvPath = platform === 'win32'
? path.join(homeDir, '.local', 'bin', 'uv.exe')
: path.join(homeDir, '.local', 'bin', 'uv');
if (fs.existsSync(uvPath)) {
return uvPath;
} }
// Try to find uv in PATH by checking if it exists
try {
const uvInPath = spawnSync('uv', ['--version'], {
stdio: 'ignore',
});
if (uvInPath.status === 0) {
return 'uv';
}
} catch {
// uv not in PATH
}
return null; return null;
} }
/** /**
* Run the codeflash CLI via uv * Run the codeflash CLI
*/ */
function runCodeflash(args) { function runCodeflash(args) {
const uvBin = findUv(); const codeflashBin = findCodeflash();
if (!uvBin) { if (!codeflashBin) {
console.error('\x1b[31mError:\x1b[0m uv not found.'); console.error('\x1b[31mError:\x1b[0m codeflash Python CLI not found.');
console.error(''); console.error('');
console.error('Please run the setup script:'); console.error('Please run the setup script:');
console.error(' npx codeflash-setup'); console.error(' npx codeflash-setup');
console.error(''); console.error('');
console.error('Or install uv manually:');
console.error(' curl -LsSf https://astral.sh/uv/install.sh | sh');
process.exit(1); process.exit(1);
} }
// Use uv tool run to execute codeflash // Strip VIRTUAL_ENV so the venv's Python is used, not an activated one
const child = spawn(uvBin, ['tool', 'run', 'codeflash', ...args], { const env = { ...process.env };
delete env.VIRTUAL_ENV;
delete env.CONDA_PREFIX;
delete env.CONDA_DEFAULT_ENV;
const child = spawn(codeflashBin, args, {
stdio: 'inherit', stdio: 'inherit',
env,
}); });
child.on('error', (error) => { child.on('error', (error) => {
@ -87,19 +70,15 @@ function runCodeflash(args) {
*/ */
function showSetupHelp() { function showSetupHelp() {
console.log(` console.log(`
\x1b[36m \x1b[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
Codeflash CLI Setup Required \u2551 Codeflash CLI Setup Required \u2551
\x1b[0m \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1b[0m
The codeflash Python CLI is not installed. The codeflash Python CLI is not installed.
\x1b[33mTo complete setup, run:\x1b[0m \x1b[33mTo complete setup, run:\x1b[0m
npx codeflash-setup npx codeflash-setup
\x1b[33mOr install manually:\x1b[0m
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install codeflash
\x1b[36mDocumentation:\x1b[0m https://docs.codeflash.ai \x1b[36mDocumentation:\x1b[0m https://docs.codeflash.ai
`); `);
} }
@ -111,18 +90,8 @@ const args = process.argv.slice(2);
if (args[0] === 'setup' || args[0] === '--setup') { if (args[0] === 'setup' || args[0] === '--setup') {
require('../scripts/postinstall.js'); require('../scripts/postinstall.js');
} else { } else {
// Check if codeflash is installed const codeflashBin = findCodeflash();
const uvBin = findUv(); if (!codeflashBin && args.length === 0) {
if (uvBin) {
const check = spawnSync(uvBin, ['tool', 'run', 'codeflash', '--version'], {
stdio: 'ignore',
});
if (check.status !== 0 && args.length === 0) {
showSetupHelp();
process.exit(1);
}
} else if (args.length === 0) {
showSetupHelp(); showSetupHelp();
process.exit(1); process.exit(1);
} }

View file

@ -0,0 +1,63 @@
/**
* Shared path utilities for codeflash installation.
*
* Provides OS-specific cache directory and binary paths used by
* both the postinstall script and the CLI entry point.
*/
const os = require('os');
const path = require('path');
/**
* Get the OS-appropriate cache directory for codeflash.
*
* - Linux: ~/.cache/codeflash
* - macOS: ~/Library/Caches/codeflash
* - Windows: %LOCALAPPDATA%\codeflash
*/
function getCacheDir() {
const platform = os.platform();
const homeDir = os.homedir();
if (platform === 'win32') {
return path.join(process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'), 'codeflash');
}
if (platform === 'darwin') {
return path.join(homeDir, 'Library', 'Caches', 'codeflash');
}
// Linux / other Unix
return path.join(process.env.XDG_CACHE_HOME || path.join(homeDir, '.cache'), 'codeflash');
}
/**
* Get the path to the venv directory inside the cache.
*/
function getVenvDir() {
return path.join(getCacheDir(), 'venv');
}
/**
* Get the path to the codeflash binary inside the venv.
*/
function getCodeflashBin() {
const venvDir = getVenvDir();
if (os.platform() === 'win32') {
return path.join(venvDir, 'Scripts', 'codeflash.exe');
}
return path.join(venvDir, 'bin', 'codeflash');
}
/**
* Get the path to the uv binary.
*/
function getUvPath() {
const platform = os.platform();
const homeDir = os.homedir();
if (platform === 'win32') {
return path.join(homeDir, '.local', 'bin', 'uv.exe');
}
return path.join(homeDir, '.local', 'bin', 'uv');
}
module.exports = { getCacheDir, getVenvDir, getCodeflashBin, getUvPath };

View file

@ -6,16 +6,27 @@
* This script runs after `npm install codeflash` and: * This script runs after `npm install codeflash` and:
* 1. Checks if uv (Python package manager) is installed * 1. Checks if uv (Python package manager) is installed
* 2. If not, installs uv automatically * 2. If not, installs uv automatically
* 3. Uses uv to install the Python codeflash CLI * 3. Creates a dedicated venv and installs the Python codeflash CLI into it
* *
* This approach follows the same pattern as aider and mistral-code, * The codeflash Python CLI is installed into an isolated venv at:
* which use uv for Python distribution. * - Linux: ~/.cache/codeflash/venv/
* - macOS: ~/Library/Caches/codeflash/venv/
* - Windows: %LOCALAPPDATA%\codeflash\venv\
*/ */
const { execSync, spawnSync } = require('child_process'); const { execSync, spawnSync } = require('child_process');
const os = require('os'); const os = require('os');
const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { getCacheDir, getVenvDir, getCodeflashBin, getUvPath } = require('./paths');
// Clean environment without VIRTUAL_ENV so uv doesn't target an activated venv
const cleanEnv = (() => {
const env = { ...process.env };
delete env.VIRTUAL_ENV;
delete env.CONDA_PREFIX;
delete env.CONDA_DEFAULT_ENV;
return env;
})();
// ANSI color codes for pretty output // ANSI color codes for pretty output
const colors = { const colors = {
@ -36,15 +47,15 @@ function logStep(step, message) {
} }
function logSuccess(message) { function logSuccess(message) {
console.log(`${colors.green}${colors.reset} ${message}`); console.log(`${colors.green}\u2713${colors.reset} ${message}`);
} }
function logWarning(message) { function logWarning(message) {
console.log(`${colors.yellow}${colors.reset} ${message}`); console.log(`${colors.yellow}\u26A0${colors.reset} ${message}`);
} }
function logError(message) { function logError(message) {
console.error(`${colors.red}${colors.reset} ${message}`); console.error(`${colors.red}\u2717${colors.reset} ${message}`);
} }
/** /**
@ -62,20 +73,6 @@ function commandExists(command) {
} }
} }
/**
* Get the uv binary path
* uv installs to ~/.local/bin on Unix or %USERPROFILE%\.local\bin on Windows
*/
function getUvPath() {
const platform = os.platform();
const homeDir = os.homedir();
if (platform === 'win32') {
return path.join(homeDir, '.local', 'bin', 'uv.exe');
}
return path.join(homeDir, '.local', 'bin', 'uv');
}
/** /**
* Install uv using the official installer * Install uv using the official installer
*/ */
@ -86,13 +83,11 @@ function installUv() {
try { try {
if (platform === 'win32') { if (platform === 'win32') {
// Windows: Use PowerShell
execSync( execSync(
'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"',
{ stdio: 'inherit', shell: true } { stdio: 'inherit', shell: true }
); );
} else { } else {
// macOS/Linux: Use curl
execSync( execSync(
'curl -LsSf https://astral.sh/uv/install.sh | sh', 'curl -LsSf https://astral.sh/uv/install.sh | sh',
{ stdio: 'inherit', shell: true } { stdio: 'inherit', shell: true }
@ -122,26 +117,43 @@ function hasGit() {
} }
/** /**
* Install codeflash Python CLI using uv tool * Create the codeflash venv and install the Python CLI into it.
* *
* Installation priority: * Installation priority:
* 1. GitHub main branch (if git available) - gets latest features * 1. GitHub main branch (if git available) - gets latest features
* 2. PyPI (fallback) - stable release * 2. PyPI (fallback) - stable release
*
* We prefer GitHub because it has the latest JS/TS support that may not
* be published to PyPI yet. uv handles cloning internally in its cache.
*/ */
function installCodeflash(uvBin) { function installCodeflash(uvBin) {
logStep('2/3', 'Installing codeflash Python CLI...'); logStep('2/3', 'Installing codeflash Python CLI...');
const venvDir = getVenvDir();
const cacheDir = getCacheDir();
// Ensure cache directory exists
fs.mkdirSync(cacheDir, { recursive: true });
// Create the venv (or reuse existing)
try {
execSync(`"${uvBin}" venv --python python3.12 --clear "${venvDir}"`, {
stdio: 'inherit',
shell: true,
env: cleanEnv,
});
logSuccess(`venv created at ${venvDir}`);
} catch (error) {
logError(`Failed to create venv: ${error.message}`);
return false;
}
const GITHUB_REPO = 'git+https://github.com/codeflash-ai/codeflash.git'; const GITHUB_REPO = 'git+https://github.com/codeflash-ai/codeflash.git';
// Priority 1: Install from GitHub (latest features, requires git) // Priority 1: Install from GitHub (latest features, requires git)
if (hasGit()) { if (hasGit()) {
try { try {
execSync(`"${uvBin}" tool install --force --python python3.12 "${GITHUB_REPO}"`, { execSync(`"${uvBin}" pip install --python "${venvDir}" "${GITHUB_REPO}"`, {
stdio: 'inherit', stdio: 'inherit',
shell: true, shell: true,
env: cleanEnv,
}); });
logSuccess('codeflash CLI installed from GitHub (latest)'); logSuccess('codeflash CLI installed from GitHub (latest)');
return true; return true;
@ -155,9 +167,10 @@ function installCodeflash(uvBin) {
// Priority 2: Install from PyPI (stable release fallback) // Priority 2: Install from PyPI (stable release fallback)
try { try {
execSync(`"${uvBin}" tool install --force --python python3.12 codeflash`, { execSync(`"${uvBin}" pip install --python "${venvDir}" codeflash`, {
stdio: 'inherit', stdio: 'inherit',
shell: true, shell: true,
env: cleanEnv,
}); });
logSuccess('codeflash CLI installed from PyPI'); logSuccess('codeflash CLI installed from PyPI');
return true; return true;
@ -167,34 +180,19 @@ function installCodeflash(uvBin) {
} }
} }
/**
* Update shell configuration to include uv tools in PATH
*/
function updateShellPath(uvBin) {
logStep('3/3', 'Updating shell configuration...');
try {
execSync(`"${uvBin}" tool update-shell`, {
stdio: 'inherit',
shell: true,
});
logSuccess('Shell configuration updated');
return true;
} catch (error) {
logWarning(`Could not update shell: ${error.message}`);
logWarning('You may need to add ~/.local/bin to your PATH manually');
return true; // Non-fatal
}
}
/** /**
* Verify the installation works * Verify the installation works
*/ */
function verifyInstallation(uvBin) { function verifyInstallation() {
const codeflashBin = getCodeflashBin();
try { try {
const result = spawnSync(uvBin, ['tool', 'run', 'codeflash', '--version'], { if (!fs.existsSync(codeflashBin)) {
return false;
}
const result = spawnSync(codeflashBin, ['--version'], {
encoding: 'utf8', encoding: 'utf8',
shell: true, shell: true,
env: cleanEnv,
}); });
if (result.status === 0) { if (result.status === 0) {
@ -213,9 +211,9 @@ function verifyInstallation(uvBin) {
*/ */
async function main() { async function main() {
console.log(''); console.log('');
log('╔════════════════════════════════════════════╗', 'cyan'); log('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557', 'cyan');
log('║ Codeflash CLI Installation ║', 'cyan'); log('\u2551 Codeflash CLI Installation \u2551', 'cyan');
log('╚════════════════════════════════════════════╝', 'cyan'); log('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D', 'cyan');
console.log(''); console.log('');
// Check if running in CI or with --ignore-scripts // Check if running in CI or with --ignore-scripts
@ -230,7 +228,7 @@ async function main() {
// Step 1: Check/install uv // Step 1: Check/install uv
if (commandExists('uv')) { if (commandExists('uv')) {
logSuccess('uv is already installed'); logSuccess('uv is already installed');
uvBin = 'uv'; // Use the one in PATH uvBin = 'uv';
} else if (fs.existsSync(uvBin)) { } else if (fs.existsSync(uvBin)) {
logSuccess('uv found at ' + uvBin); logSuccess('uv found at ' + uvBin);
} else { } else {
@ -240,7 +238,6 @@ async function main() {
process.exit(1); process.exit(1);
} }
// Check if uv is now available
if (!fs.existsSync(uvBin) && !commandExists('uv')) { if (!fs.existsSync(uvBin) && !commandExists('uv')) {
logError('uv installation completed but binary not found'); logError('uv installation completed but binary not found');
logError('Please restart your terminal and run: npx codeflash-setup'); logError('Please restart your terminal and run: npx codeflash-setup');
@ -253,25 +250,23 @@ async function main() {
uvBin = 'uv'; uvBin = 'uv';
} }
// Step 2: Install codeflash Python CLI // Step 2: Install codeflash Python CLI into dedicated venv
if (!installCodeflash(uvBin)) { if (!installCodeflash(uvBin)) {
logError('Failed to install codeflash CLI'); logError('Failed to install codeflash CLI');
logError('You can try manually: uv tool install codeflash'); logError('You can try manually: uv pip install codeflash');
process.exit(1); process.exit(1);
} }
// Step 3: Update shell PATH
updateShellPath(uvBin);
// Verify installation // Verify installation
console.log(''); console.log('');
verifyInstallation(uvBin); logStep('3/3', 'Verifying installation...');
verifyInstallation();
// Print success message // Print success message
console.log(''); console.log('');
log('════════════════════════════════════════════', 'green'); log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550', 'green');
logSuccess('Codeflash installation complete!'); logSuccess('Codeflash installation complete!');
log('════════════════════════════════════════════', 'green'); log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550', 'green');
console.log(''); console.log('');
log('Get started:', 'cyan'); log('Get started:', 'cyan');
console.log(' npx codeflash --help'); console.log(' npx codeflash --help');