mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-08-04 13:16:06 +00:00
Replace Node.js https.get with curl for 15x faster downloads. GitHub's redirect chain causes excessive TLS handshake overhead in Node's http client.
198 lines
5.8 KiB
JavaScript
198 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const { existsSync, mkdirSync, chmodSync, unlinkSync, renameSync, writeFileSync, symlinkSync, lstatSync } = require('fs');
|
|
const { readFileSync } = require('fs');
|
|
const { join } = require('path');
|
|
const { platform, arch } = require('os');
|
|
const { execSync } = require('child_process');
|
|
const { createHash } = require('crypto');
|
|
|
|
const projectRoot = join(__dirname, '..');
|
|
const binDir = join(projectRoot, 'bin');
|
|
const packageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
|
|
const version = packageJson.version;
|
|
|
|
const GITHUB_REPO = 'lahfir/agent-desktop';
|
|
|
|
const TARGET_MAP = {
|
|
'darwin-arm64': 'aarch64-apple-darwin',
|
|
'darwin-x64': 'x86_64-apple-darwin',
|
|
'linux-x64': 'x86_64-unknown-linux-gnu',
|
|
'linux-arm64': 'aarch64-unknown-linux-gnu',
|
|
'win32-x64': 'x86_64-pc-windows-msvc',
|
|
};
|
|
|
|
const BINARY_NAME_MAP = {
|
|
'darwin-arm64': 'agent-desktop-darwin-arm64',
|
|
'darwin-x64': 'agent-desktop-darwin-x64',
|
|
'linux-x64': 'agent-desktop-linux-x64',
|
|
'linux-arm64': 'agent-desktop-linux-arm64',
|
|
'win32-x64': 'agent-desktop-win32-x64.exe',
|
|
};
|
|
|
|
const SUPPORTED_PLATFORMS = ['darwin'];
|
|
|
|
function log(msg) {
|
|
process.stderr.write(`agent-desktop: ${msg}\n`);
|
|
}
|
|
|
|
function getPlatformKey() {
|
|
return `${platform()}-${arch()}`;
|
|
}
|
|
|
|
function download(url, dest) {
|
|
const tmpDest = dest + '.tmp';
|
|
try {
|
|
execSync(`curl -fsSL --retry 3 --retry-delay 2 -o "${tmpDest}" "${url}"`, {
|
|
stdio: 'pipe',
|
|
timeout: 60000,
|
|
});
|
|
renameSync(tmpDest, dest);
|
|
} catch (err) {
|
|
try { unlinkSync(tmpDest); } catch {}
|
|
throw new Error(`Failed to download ${url}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
function verifyChecksum(filePath, expectedHash) {
|
|
const fileBuffer = readFileSync(filePath);
|
|
const hash = createHash('sha256').update(fileBuffer).digest('hex');
|
|
return hash === expectedHash;
|
|
}
|
|
|
|
function fixGlobalInstallBin() {
|
|
if (platform() === 'win32') return;
|
|
|
|
let npmBinDir;
|
|
try {
|
|
const prefix = execSync('npm prefix -g', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
npmBinDir = join(prefix, 'bin');
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
const symlinkPath = join(npmBinDir, 'agent-desktop');
|
|
const platformKey = getPlatformKey();
|
|
const binaryName = BINARY_NAME_MAP[platformKey];
|
|
if (!binaryName) return;
|
|
|
|
const binaryPath = join(binDir, binaryName);
|
|
|
|
try {
|
|
const stat = lstatSync(symlinkPath);
|
|
if (!stat.isSymbolicLink()) return;
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
unlinkSync(symlinkPath);
|
|
symlinkSync(binaryPath, symlinkPath);
|
|
log('Optimized: symlink points to native binary (zero overhead)');
|
|
} catch (err) {
|
|
log(`Could not optimize symlink: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
if (process.env.AGENT_DESKTOP_SKIP_DOWNLOAD === '1') {
|
|
log('Skipping binary download (AGENT_DESKTOP_SKIP_DOWNLOAD=1)');
|
|
return;
|
|
}
|
|
|
|
const platformKey = getPlatformKey();
|
|
const target = TARGET_MAP[platformKey];
|
|
const binaryName = BINARY_NAME_MAP[platformKey];
|
|
|
|
if (!SUPPORTED_PLATFORMS.includes(platform()) || !target || !binaryName) {
|
|
log('agent-desktop currently supports macOS only.');
|
|
log('Windows and Linux support is coming in Phase 2.');
|
|
log(`See: https://github.com/${GITHUB_REPO}`);
|
|
return;
|
|
}
|
|
|
|
const binaryPath = join(binDir, binaryName);
|
|
|
|
if (process.env.AGENT_DESKTOP_BINARY_PATH) {
|
|
const customPath = process.env.AGENT_DESKTOP_BINARY_PATH;
|
|
if (existsSync(customPath)) {
|
|
try {
|
|
writeFileSync(binaryPath, readFileSync(customPath));
|
|
chmodSync(binaryPath, 0o755);
|
|
log(`Using binary from AGENT_DESKTOP_BINARY_PATH: ${customPath}`);
|
|
fixGlobalInstallBin();
|
|
return;
|
|
} catch (err) {
|
|
log(`Failed to copy from AGENT_DESKTOP_BINARY_PATH: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (existsSync(binaryPath)) {
|
|
chmodSync(binaryPath, 0o755);
|
|
log(`Native binary ready: ${binaryName}`);
|
|
fixGlobalInstallBin();
|
|
return;
|
|
}
|
|
|
|
if (!existsSync(binDir)) {
|
|
mkdirSync(binDir, { recursive: true });
|
|
}
|
|
|
|
const tarball = `agent-desktop-v${version}-${target}.tar.gz`;
|
|
const baseUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${version}`;
|
|
const tarballUrl = `${baseUrl}/${tarball}`;
|
|
const checksumsUrl = `${baseUrl}/checksums.txt`;
|
|
const tarballPath = join(binDir, tarball);
|
|
const checksumsPath = join(binDir, 'checksums.txt');
|
|
|
|
log(`Downloading native binary for ${platformKey}...`);
|
|
|
|
try {
|
|
download(tarballUrl, tarballPath);
|
|
|
|
try {
|
|
download(checksumsUrl, checksumsPath);
|
|
const checksums = readFileSync(checksumsPath, 'utf8');
|
|
const expectedLine = checksums.split('\n').find((line) => line.includes(tarball));
|
|
if (expectedLine) {
|
|
const expectedHash = expectedLine.split(/\s+/)[0];
|
|
if (!verifyChecksum(tarballPath, expectedHash)) {
|
|
log('WARNING: Checksum verification failed.');
|
|
unlinkSync(tarballPath);
|
|
unlinkSync(checksumsPath);
|
|
return;
|
|
}
|
|
log('Checksum verified');
|
|
}
|
|
unlinkSync(checksumsPath);
|
|
} catch {
|
|
log('Checksum verification skipped');
|
|
}
|
|
|
|
execSync(`tar -xzf "${tarballPath}" -C "${binDir}"`, { stdio: 'pipe' });
|
|
|
|
const extractedBinary = join(binDir, 'agent-desktop');
|
|
if (existsSync(extractedBinary) && extractedBinary !== binaryPath) {
|
|
renameSync(extractedBinary, binaryPath);
|
|
}
|
|
|
|
chmodSync(binaryPath, 0o755);
|
|
unlinkSync(tarballPath);
|
|
log(`Installed native binary: ${binaryName}`);
|
|
} catch (err) {
|
|
log(`Could not download native binary: ${err.message}`);
|
|
log('');
|
|
log('Download manually from:');
|
|
log(` ${tarballUrl}`);
|
|
log(`Then place at: ${binaryPath}`);
|
|
|
|
try { if (existsSync(tarballPath)) unlinkSync(tarballPath); } catch {}
|
|
try { if (existsSync(checksumsPath)) unlinkSync(checksumsPath); } catch {}
|
|
return;
|
|
}
|
|
|
|
fixGlobalInstallBin();
|
|
}
|
|
|
|
main();
|