Files
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

232 lines
8.3 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const https = require("https");
const { createGunzip } = require("zlib");
const crypto = require("crypto");
const REPO = "yvgude/lean-ctx";
const BIN_DIR = path.join(__dirname, "bin");
const IS_WIN = process.platform === "win32";
const BINARY_NAME = IS_WIN ? "lean-ctx.exe" : "lean-ctx";
const BINARY_PATH = path.join(BIN_DIR, BINARY_NAME);
function getGlibcVersion() {
try {
const out = execSync("ldd --version 2>&1 || true", { encoding: "utf8" });
const match = out.match(/(\d+)\.(\d+)\s*$/m);
if (match) return { major: parseInt(match[1]), minor: parseInt(match[2]) };
} catch {}
return null;
}
function getTarget() {
const platform = process.platform;
const arch = process.arch;
if (platform === "linux") {
let libc = "musl";
const glibc = getGlibcVersion();
if (glibc && (glibc.major > 2 || (glibc.major === 2 && glibc.minor >= 35))) {
libc = "gnu";
}
const archMap = { x64: "x86_64", arm64: "aarch64" };
const rustArch = archMap[arch];
if (!rustArch) {
console.error(`Unsupported architecture: ${arch}`);
process.exit(1);
}
return `${rustArch}-unknown-linux-${libc}`;
}
const key = `${platform}-${arch}`;
const targets = {
"darwin-x64": "x86_64-apple-darwin",
"darwin-arm64": "aarch64-apple-darwin",
"win32-x64": "x86_64-pc-windows-msvc",
};
const target = targets[key];
if (!target) {
console.error(`Unsupported platform: ${key}`);
console.error("Build from source instead: cargo install lean-ctx");
process.exit(1);
}
return target;
}
function httpsGet(url) {
return new Promise((resolve, reject) => {
const get = (u) => {
https.get(u, { headers: { "User-Agent": "lean-ctx-bin-npm" } }, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
get(res.headers.location);
return;
}
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode} for ${u}`));
return;
}
resolve(res);
}).on("error", reject);
};
get(url);
});
}
function httpsGetJson(url) {
return new Promise((resolve, reject) => {
httpsGet(url).then((res) => {
let data = "";
res.on("data", (c) => (data += c));
res.on("end", () => {
try { resolve(JSON.parse(data)); }
catch (e) { reject(e); }
});
}).catch(reject);
});
}
async function downloadToFile(url, dest) {
const res = await httpsGet(url);
return new Promise((resolve, reject) => {
const ws = fs.createWriteStream(dest);
res.pipe(ws);
ws.on("finish", resolve);
ws.on("error", reject);
});
}
function extractTarGz(archive, destDir, binaryName) {
const gunzip = createGunzip();
const input = fs.createReadStream(archive);
return new Promise((resolve, reject) => {
const chunks = [];
input.pipe(gunzip)
.on("data", (c) => chunks.push(c))
.on("end", () => {
const buf = Buffer.concat(chunks);
let offset = 0;
while (offset < buf.length) {
const header = buf.subarray(offset, offset + 512);
if (header.every((b) => b === 0)) break;
const name = header.subarray(0, 100).toString("utf8").replace(/\0/g, "");
const sizeStr = header.subarray(124, 136).toString("utf8").replace(/\0/g, "").trim();
const size = parseInt(sizeStr, 8) || 0;
offset += 512;
const baseName = path.basename(name);
if (baseName === binaryName && size > 0) {
const dest = path.join(destDir, binaryName);
fs.writeFileSync(dest, buf.subarray(offset, offset + size));
if (!IS_WIN) fs.chmodSync(dest, 0o755);
resolve(dest);
return;
}
offset += Math.ceil(size / 512) * 512;
}
reject(new Error(`${binaryName} not found in archive`));
})
.on("error", reject);
});
}
async function main() {
if (fs.existsSync(BINARY_PATH)) {
console.log("lean-ctx binary already exists, skipping download");
return;
}
const target = getTarget();
console.log(`lean-ctx: installing for ${target}...`);
const release = await httpsGetJson(`https://api.github.com/repos/${REPO}/releases/latest`);
const tag = release.tag_name;
console.log(`lean-ctx: latest release ${tag}`);
const ext = IS_WIN ? ".zip" : ".tar.gz";
const assetName = `lean-ctx-${target}${ext}`;
const asset = (release.assets || []).find((a) => a.name === assetName);
if (!asset) {
console.error(`No binary for ${target}. Install from source: cargo install lean-ctx`);
process.exit(1);
}
const tmpDir = fs.mkdtempSync(path.join(require("os").tmpdir(), "lean-ctx-"));
const archivePath = path.join(tmpDir, assetName);
try {
await downloadToFile(asset.browser_download_url, archivePath);
const sumsAsset = (release.assets || []).find((a) => a.name === "SHA256SUMS");
if (sumsAsset) {
const sumsText = await new Promise((resolve, reject) => {
httpsGet(sumsAsset.browser_download_url).then((res) => {
let data = "";
res.on("data", (c) => (data += c));
res.on("end", () => resolve(data));
}).catch(reject);
});
const expectedLine = sumsText.split("\n").find((l) => l.includes(assetName));
if (expectedLine) {
const expectedHash = expectedLine.trim().split(/\s+/)[0].toLowerCase();
const fileHash = crypto.createHash("sha256").update(fs.readFileSync(archivePath)).digest("hex");
if (fileHash !== expectedHash) {
throw new Error(`SHA256 mismatch: expected ${expectedHash}, got ${fileHash}. Binary may be compromised.`);
}
console.log("lean-ctx: SHA256 verified");
}
}
console.log("lean-ctx: downloaded, extracting...");
fs.mkdirSync(BIN_DIR, { recursive: true });
if (IS_WIN) {
execSync(`tar -xf "${archivePath}" -C "${BIN_DIR}"`, { stdio: "ignore" });
} else {
await extractTarGz(archivePath, BIN_DIR, "lean-ctx");
}
console.log(`lean-ctx: installed to ${BINARY_PATH}`);
// Auto-onboard unless in CI or opted out
if (!process.env.CI && process.env.LEAN_CTX_NO_ONBOARD !== "1") {
try {
const { execSync: exec } = require("child_process");
console.log("");
console.log("Running onboard (connecting your AI tools)...");
exec(`"${BINARY_PATH}" onboard`, { stdio: "inherit", timeout: 30000 });
} catch {
// Non-fatal: onboard may fail in restricted envs
}
}
console.log("");
console.log("\x1b[1m┌─────────────────────────────────────────────────────┐\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[32m\x1b[1m✓ lean-ctx installed successfully\x1b[0m \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m Quick start: \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[1mlean-ctx wrap cursor\x1b[0m (one-command setup) \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[1mlean-ctx wrap claude\x1b[0m (Claude Code) \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[1mlean-ctx wrap codex\x1b[0m (Codex CLI) \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m Full control: \x1b[2mlean-ctx setup\x1b[0m \x1b[1m│\x1b[0m");
console.log("\x1b[1m│\x1b[0m \x1b[2mDocs: https://leanctx.com/docs\x1b[0m \x1b[1m│\x1b[0m");
console.log("\x1b[1m└─────────────────────────────────────────────────────┘\x1b[0m");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}
main().catch((err) => {
console.error(`lean-ctx: installation failed: ${err.message}`);
console.error("Install from source instead: cargo install lean-ctx");
process.exit(1);
});