diff --git a/.changeset/remove-packaged-bt.md b/.changeset/remove-packaged-bt.md new file mode 100644 index 000000000..3f6d26903 --- /dev/null +++ b/.changeset/remove-packaged-bt.md @@ -0,0 +1,5 @@ +--- +"braintrust": major +--- + +ref!: Remove packaged `bt` binary from `braintrust` package diff --git a/README.md b/README.md index a8f2c7cdd..f72e5da85 100644 --- a/README.md +++ b/README.md @@ -34,25 +34,25 @@ Eval("Say Hi Bot", { }); ``` -Run it: +Install the `bt` CLI separately using the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart), then run it: ```bash -BRAINTRUST_API_KEY= npx bt eval tutorial.eval.ts +BRAINTRUST_API_KEY= bt eval tutorial.eval.ts ``` ## Packages | Package | Purpose | npm | Docs | | ---------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -| `braintrust` | Core JavaScript/TypeScript SDK for logging, tracing, evals, and CLI. | [![npm: braintrust](https://img.shields.io/npm/v/braintrust.svg)](https://www.npmjs.com/package/braintrust) | [js/README.md](js/README.md) | +| `braintrust` | Core JavaScript/TypeScript SDK for logging, tracing, and evals. | [![npm: braintrust](https://img.shields.io/npm/v/braintrust.svg)](https://www.npmjs.com/package/braintrust) | [js/README.md](js/README.md) | | `@braintrust/browser` | Browser-focused SDK integration with AsyncLocalStorage polyfill support. | [![npm: @braintrust/browser](https://img.shields.io/npm/v/%40braintrust%2Fbrowser.svg)](https://www.npmjs.com/package/@braintrust/browser) | [integrations/browser-js/README.md](integrations/browser-js/README.md) | | `@braintrust/otel` | OpenTelemetry span processor and compatibility helpers for Braintrust tracing. | [![npm: @braintrust/otel](https://img.shields.io/npm/v/%40braintrust%2Fotel.svg)](https://www.npmjs.com/package/@braintrust/otel) | [integrations/otel-js/README.md](integrations/otel-js/README.md) | | `@braintrust/temporal` | Temporal client/worker plugin and workflow interceptors for Braintrust tracing. | [![npm: @braintrust/temporal](https://img.shields.io/npm/v/%40braintrust%2Ftemporal.svg)](https://www.npmjs.com/package/@braintrust/temporal) | [integrations/temporal-js/README.md](integrations/temporal-js/README.md) | ## CLI `bt` -The `bt` cli is installed alongside `braintrust`, use it with `npx bt` or `pnpm exec bt`. -See the [documentation](https://www.braintrust.dev/docs/reference/cli/quickstart). +The `bt` CLI is installed separately from `braintrust`. +See the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart). ## Documentation diff --git a/js/README.md b/js/README.md index 052250d14..0249838d3 100644 --- a/js/README.md +++ b/js/README.md @@ -14,6 +14,9 @@ Install the SDK: npm install braintrust ``` +The `bt` CLI is installed separately from the SDK. +See the standalone installation instructions in the [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart). + ## Quickstart Run a simple experiment (replace `YOUR_API_KEY` with your Braintrust API key): diff --git a/js/bin/bt b/js/bin/bt deleted file mode 100755 index 5741f84d4..000000000 --- a/js/bin/bt +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node -"use strict"; - -// Runtime launcher for the `bt` CLI. Resolves the platform-specific binary -// (installed via the matching `@braintrust/bt-*` optionalDependency, or -// downloaded by `scripts/install.js` as a fallback) and forwards argv, -// stdio, signals, and the exit code. - -const childProcess = require("node:child_process"); -const { getBinaryPath } = require("../scripts/bt-helper"); - -let binaryPath; -try { - binaryPath = getBinaryPath(); -} catch (err) { - console.error(err.message); - process.exit(1); -} - -const child = childProcess - .spawn(binaryPath, process.argv.slice(2), { - stdio: "inherit", - windowsHide: true, - }) - .on("error", (err) => { - console.error(err); - process.exit(1); - }) - .on("exit", (code, signal) => { - if (signal) { - // Detach our forwarding listener so the re-raised signal hits Node's - // default handler; otherwise we'd intercept it again and exit 0. - process.removeAllListeners(signal); - process.kill(process.pid, signal); - return; - } - process.exit(code ?? 1); - }); - -process.on("SIGTERM", () => child.kill("SIGTERM")); -process.on("SIGINT", () => child.kill("SIGINT")); diff --git a/js/package.json b/js/package.json index d4e150877..27b87cfa5 100644 --- a/js/package.json +++ b/js/package.json @@ -18,9 +18,6 @@ "./dist/index.mjs": "./dist/browser.mjs", "./dist/index.d.mts": "./dist/browser.d.mts" }, - "bin": { - "bt": "./bin/bt" - }, "exports": { "./package.json": "./package.json", ".": { @@ -111,14 +108,10 @@ }, "files": [ "dist/**/*", - "bin/bt", - "scripts/bt-helper.js", - "scripts/install.js", "NOTICE", "licenses/**/*" ], "scripts": { - "postinstall": "node ./scripts/install.js", "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsup", "check:typings": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "watch": "tsup --watch", @@ -201,15 +194,6 @@ "peerDependencies": { "zod": "^3.25.34 || ^4.0" }, - "optionalDependencies": { - "@braintrust/bt-darwin-arm64": "0.12.0", - "@braintrust/bt-darwin-x64": "0.12.0", - "@braintrust/bt-linux-arm64": "0.12.0", - "@braintrust/bt-linux-x64": "0.12.0", - "@braintrust/bt-linux-x64-musl": "0.12.0", - "@braintrust/bt-win32-arm64": "0.12.0", - "@braintrust/bt-win32-x64": "0.12.0" - }, "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/", diff --git a/js/scripts/bt-helper.js b/js/scripts/bt-helper.js deleted file mode 100644 index 3502e5e07..000000000 --- a/js/scripts/bt-helper.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; - -// Shared helpers for locating the `bt` binary. Used by both `bin/bt` -// (the runtime launcher) and `scripts/install.js` (the postinstall -// fallback downloader). - -const fs = require("node:fs"); -const os = require("node:os"); -const path = require("node:path"); - -const BINARY_DISTRIBUTIONS = [ - { packageName: "@braintrust/bt-darwin-arm64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-darwin-x64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-arm64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-x64", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-linux-x64-musl", subpath: "bin/bt" }, - { packageName: "@braintrust/bt-win32-arm64", subpath: "bin/bt.exe" }, - { packageName: "@braintrust/bt-win32-x64", subpath: "bin/bt.exe" }, -]; - -function detectLibc() { - if (process.platform !== "linux") return null; - try { - const report = process.report && process.report.getReport(); - if (report && report.header && report.header.glibcVersionRuntime) { - return "glibc"; - } - return "musl"; - } catch { - return "glibc"; - } -} - -function binaryName() { - return process.platform === "win32" ? "bt.exe" : "bt"; -} - -function getDistributionForThisPlatform() { - const arch = os.arch(); - const platform = os.platform(); - const subpath = `bin/${binaryName()}`; - - let packageName; - if (platform === "darwin") { - if (arch === "arm64") packageName = "@braintrust/bt-darwin-arm64"; - else if (arch === "x64") packageName = "@braintrust/bt-darwin-x64"; - } else if (platform === "linux") { - if (arch === "arm64") { - packageName = "@braintrust/bt-linux-arm64"; - } else if (arch === "x64") { - packageName = - detectLibc() === "musl" - ? "@braintrust/bt-linux-x64-musl" - : "@braintrust/bt-linux-x64"; - } - } else if (platform === "win32") { - if (arch === "arm64") packageName = "@braintrust/bt-win32-arm64"; - else if (arch === "x64") packageName = "@braintrust/bt-win32-x64"; - } - - return { packageName, subpath }; -} - -function throwUnsupportedPlatformError() { - throw new Error( - `Unsupported operating system or architecture! The bt CLI does not work on ${process.platform}-${process.arch}. - -bt supports: -- macOS (darwin) on arm64 and x64 -- Linux on arm64 and x64 (glibc and musl) -- Windows on arm64 and x64`, - ); -} - -// Constructed indirectly so bundlers (e.g. @vercel/nft) don't statically -// detect the fallback binary path as an asset to trace. -function getFallbackBinaryPath() { - const parts = [__dirname, binaryName()]; - return path.resolve(...parts); -} - -function getBinaryPath() { - if (process.env.BT_BINARY_PATH) { - return process.env.BT_BINARY_PATH; - } - - const { packageName, subpath } = getDistributionForThisPlatform(); - - if (packageName === undefined) { - throwUnsupportedPlatformError(); - } - - // Prefer the optional dep so a stale fallback from a prior - // `--omit=optional` install can't shadow a newer optional dep on upgrade. - try { - return require.resolve(`${packageName}/${subpath}`); - } catch (e) { - const fallbackBinaryPath = getFallbackBinaryPath(); - if (fs.existsSync(fallbackBinaryPath)) { - return fallbackBinaryPath; - } - - const otherInstalled = BINARY_DISTRIBUTIONS.find((dist) => { - try { - require.resolve(`${dist.packageName}/${dist.subpath}`); - return true; - } catch { - return false; - } - }); - - // Error messages inspired by esbuild: - // https://github.com/evanw/esbuild/blob/f3d535262e3998d845d0f102b944ecd5a9efda57/lib/npm/node-platform.ts#L150 - if (otherInstalled) { - throw new Error( - `bt binary for this platform/architecture not found! - -The "${otherInstalled.packageName}" package is installed, but for the current platform you should have the "${packageName}" package installed instead. This usually happens if "braintrust" is installed on one platform (for example macOS or Windows) and the "node_modules" folder is then reused on another (for example Linux in Docker). - -To fix this, avoid copying the "node_modules" folder, and instead freshly install your dependencies on the target system. You can also configure your package manager to install the right package. For example, yarn has the "supportedArchitectures" feature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitecture.`, - ); - } - - throw new Error( - `bt binary for this platform/architecture not found! - -It seems like none of the "braintrust" package's optional dependencies got installed. Please make sure your package manager is configured to install optional dependencies. If you are using npm, don't set the "--no-optional", "--ignore-optional", or "--omit=optional" flags. The "braintrust" package needs the "optionalDependencies" feature in order to install the bt binary.`, - ); - } -} - -module.exports = { - BINARY_DISTRIBUTIONS, - binaryName, - getBinaryPath, - getDistributionForThisPlatform, - getFallbackBinaryPath, - throwUnsupportedPlatformError, -}; diff --git a/js/scripts/install.js b/js/scripts/install.js deleted file mode 100644 index 0f200f140..000000000 --- a/js/scripts/install.js +++ /dev/null @@ -1,153 +0,0 @@ -"use strict"; - -// Postinstall script for the `bt` CLI binary. -// -// The native binary ships in a per-platform `@braintrust/bt-*` package listed -// in `optionalDependencies`; npm/pnpm install only the one matching the host. -// If a package manager is run with `--no-optional`, `--ignore-optional`, or -// `--omit=optional`, none of those packages get installed and `bt` would be -// unusable. As a workaround, we manually fetch the matching tarball from the -// npm registry and extract the binary so the launcher can find it. - -const fs = require("node:fs"); -const https = require("node:https"); -const path = require("node:path"); -const zlib = require("node:zlib"); - -const helper = require("./bt-helper"); -const pkg = require("../package.json"); - -if (process.env.BT_SKIP_DOWNLOAD === "1") { - console.log( - "bt: skipping post-install binary download because BT_SKIP_DOWNLOAD=1 is set.", - ); - process.exit(0); -} - -const { packageName, subpath } = helper.getDistributionForThisPlatform(); - -if (packageName === undefined) { - // Don't fail the install; the launcher will surface the unsupported-platform - // error if/when the user actually tries to run `bt`. - console.error( - `bt: no prebuilt binary available for ${process.platform}-${process.arch}; the bt CLI will not be available.`, - ); - process.exit(0); -} - -try { - require.resolve(`${packageName}/${subpath}`); - // Optional dependency was installed successfully. Nothing to do. - process.exit(0); -} catch (e) { - // Fall through to the manual download path below. - console.log( - `bt: failed to locate the "${packageName}" package after installation. - -This can happen if you use an option to disable optional dependencies during installation, like "--no-optional", "--ignore-optional", or "--omit=optional". The "braintrust" package uses the "optionalDependencies" package.json feature to install the correct bt binary for your platform and operating system. This post-install script will now try to work around that by manually downloading the bt binary from the npm registry. If this fails, you need to remove the "--no-optional", "--ignore-optional", and "--omit=optional" flags for bt to work.`, - ); -} - -const version = (pkg.optionalDependencies || {})[packageName]; -if (!version) { - // Don't fail the parent install: the SDK works without `bt`, and the - // launcher errors clearly if it's actually invoked. - console.error( - `bt: cannot determine which version of "${packageName}" to download — it is not listed in the "braintrust" package's optionalDependencies. The bt CLI will not be available; the rest of the braintrust SDK is unaffected.`, - ); - process.exit(0); -} - -function fetchBuffer(url, redirectsRemaining = 5) { - return new Promise((resolve, reject) => { - https - .get(url, (response) => { - const { statusCode = 0, headers } = response; - if (statusCode >= 200 && statusCode < 300) { - const chunks = []; - response.on("data", (chunk) => chunks.push(chunk)); - response.on("end", () => resolve(Buffer.concat(chunks))); - response.on("error", reject); - return; - } - if ( - statusCode >= 300 && - statusCode < 400 && - headers.location && - redirectsRemaining > 0 - ) { - response.resume(); - fetchBuffer(headers.location, redirectsRemaining - 1).then( - resolve, - reject, - ); - return; - } - response.resume(); - reject( - new Error( - `npm registry responded with status code ${statusCode} when downloading ${url}`, - ), - ); - }) - .on("error", reject); - }); -} - -// Extracts a single file from an uncompressed tar archive. Tar archives are -// organized in 512-byte blocks: a header block (file name in bytes 0-99, -// file size in bytes 124-135 as an octal string) followed by data blocks -// padded out to the next multiple of 512. -function extractFileFromTarball(tarball, target) { - let offset = 0; - while (offset + 512 <= tarball.length) { - const header = tarball.subarray(offset, offset + 512); - offset += 512; - const fileName = header.toString("utf-8", 0, 100).replace(/\0.*/g, ""); - if (!fileName) break; - const fileSize = parseInt( - header.toString("utf-8", 124, 136).replace(/\0.*/g, ""), - 8, - ); - if (fileName === target) { - return tarball.subarray(offset, offset + fileSize); - } - offset = (offset + fileSize + 511) & ~511; - } - return null; -} - -async function downloadFallback() { - // npm tarball URLs look like: - // https://registry.npmjs.org///-/-.tgz - // where is the unscoped package name. - const tarballName = packageName.split("/").pop(); - const url = `https://registry.npmjs.org/${packageName}/-/${tarballName}-${version}.tgz`; - console.log(`bt: downloading ${packageName}@${version} from ${url}`); - - const gzipped = await fetchBuffer(url); - const tarball = zlib.gunzipSync(gzipped); - const binary = extractFileFromTarball(tarball, `package/${subpath}`); - - if (!binary) { - throw new Error( - `could not find "package/${subpath}" inside ${packageName}@${version} tarball`, - ); - } - - const fallbackBinaryPath = helper.getFallbackBinaryPath(); - fs.mkdirSync(path.dirname(fallbackBinaryPath), { recursive: true }); - fs.writeFileSync(fallbackBinaryPath, binary); - fs.chmodSync(fallbackBinaryPath, 0o755); - console.log(`bt: installed fallback binary at ${fallbackBinaryPath}`); -} - -downloadFallback().catch((err) => { - // Don't fail the parent install: airgapped/proxied CI may not reach the - // npm registry, and the SDK works without `bt`. - console.error( - `bt: failed to download fallback binary for ${packageName}@${version}: ${err.message} -The bt CLI will not be available; the rest of the braintrust SDK is unaffected.`, - ); - process.exit(0); -}); diff --git a/js/src/node/index.ts b/js/src/node/index.ts index e4a44adfe..7cf4ad1b1 100644 --- a/js/src/node/index.ts +++ b/js/src/node/index.ts @@ -42,10 +42,11 @@ * }); * ``` * - * Finally, run the script with `npx bt eval hello.eval.ts`. + * Install the `bt` CLI separately using the standalone installation instructions in the + * [CLI quickstart](https://www.braintrust.dev/docs/reference/cli/quickstart), then run the script: * * ```bash - * BRAINTRUST_API_KEY= npx bt eval hello.eval.ts + * BRAINTRUST_API_KEY= bt eval hello.eval.ts * ``` * * @module braintrust diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a36fd5d83..0ef57e08d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -421,28 +421,6 @@ importers: zod: specifier: ^3.25.34 version: 3.25.76 - optionalDependencies: - '@braintrust/bt-darwin-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-darwin-x64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-x64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-linux-x64-musl': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-win32-arm64': - specifier: 0.12.0 - version: 0.12.0 - '@braintrust/bt-win32-x64': - specifier: 0.12.0 - version: 0.12.0 js/src/wrappers/vitest: devDependencies: @@ -482,44 +460,6 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@braintrust/bt-darwin-arm64@0.12.0': - resolution: {integrity: sha512-mY6VW/3VwcOQOGN8sYHS6F0xzHTFwgZcNlj7zlQttI6OXOCGt/bhonGIqd03QBhxmj0M31ymSS7TqSeCK/RYIQ==} - cpu: [arm64] - os: [darwin] - - '@braintrust/bt-darwin-x64@0.12.0': - resolution: {integrity: sha512-woyRyDv2DfCF8+von+3X9f1cddAbFnKLSfCbEkrBQVc0ZsAM60as8nehYv7DkafJF/67yKFx/sUraV65sukesQ==} - cpu: [x64] - os: [darwin] - - '@braintrust/bt-linux-arm64@0.12.0': - resolution: {integrity: sha512-J9/7f3EIMKmFmSSQHQnAQLpUgB8YJENrOlkABjlTprBgsIYVgz7tSH7yg2P3ur+2Jem7PpGnrDxHGh/UjRfjsg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@braintrust/bt-linux-x64-musl@0.12.0': - resolution: {integrity: sha512-KnLgENOoztBXcH+mLFJe4bYzi67kSOuELOQrm4Hler35GjgaBMI1fFLIUjKRPAmyT9VIhBMhy1ICfGEFLueMEQ==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@braintrust/bt-linux-x64@0.12.0': - resolution: {integrity: sha512-HJFbUl3HYYY1Ivw8OYBeIMcIsU9r7+fC9BzLWB5FdH7881ToKee2wbbLZssMCxFbMVeUxzEo+SzGD/1Z9Gk9CA==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@braintrust/bt-win32-arm64@0.12.0': - resolution: {integrity: sha512-+7PkdBAmiqEJsWteGHP/Zs62F75PkHPA8m/ej4TOz/mHGKulUU0bZibw91KRqIB7J7jGi5ItvCiqkiGaLKLnyw==} - cpu: [arm64] - os: [win32] - - '@braintrust/bt-win32-x64@0.12.0': - resolution: {integrity: sha512-Q0c+pVUGm82n/7N8QJ5s6j/G/Q0GFzqOaTipHjkmElLbAOOEcfRH/uljdtIPNBiEQcrzqirS0GmOgiFkeQ3Txg==} - cpu: [x64] - os: [win32] - '@changesets/apply-release-plan@7.1.0': resolution: {integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==} @@ -3843,27 +3783,6 @@ snapshots: '@babel/runtime@7.28.4': {} - '@braintrust/bt-darwin-arm64@0.12.0': - optional: true - - '@braintrust/bt-darwin-x64@0.12.0': - optional: true - - '@braintrust/bt-linux-arm64@0.12.0': - optional: true - - '@braintrust/bt-linux-x64-musl@0.12.0': - optional: true - - '@braintrust/bt-linux-x64@0.12.0': - optional: true - - '@braintrust/bt-win32-arm64@0.12.0': - optional: true - - '@braintrust/bt-win32-x64@0.12.0': - optional: true - '@changesets/apply-release-plan@7.1.0': dependencies: '@changesets/config': 3.1.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd63d0ea2..ae72bc1f8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,8 +8,6 @@ packages: blockExoticSubdeps: true minimumReleaseAge: 4320 # 3 days (in minutes) -minimumReleaseAgeExclude: - - "@braintrust/bt-*" # bt binary packages, published in lockstep with braintrust trustPolicy: no-downgrade # Ignore the check for packages published more than 30 days ago (pnpm 10.27+) # Useful for older packages that pre-date provenance support