From e3f9d3cc8dda7e79cf34e30bb002e1d975a2ca31 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 13 May 2026 16:15:30 -0400 Subject: [PATCH] chore(cli): replace chalk and boxen with native formatting Use Node's native styleText for CLI colors and a small local formatter for the experiment summary box. Also pin @braintrust/browser to the workspace braintrust package so local dependency changes are reflected during installs. --- integrations/browser-js/package.json | 3 +- js/package.json | 2 - js/src/cli/index.ts | 12 +-- js/src/cli/reporters/eval.ts | 102 +++++++++++++++---------- js/src/cli/reporters/progress.ts | 4 +- pnpm-lock.yaml | 109 +-------------------------- 6 files changed, 74 insertions(+), 158 deletions(-) diff --git a/integrations/browser-js/package.json b/integrations/browser-js/package.json index e67a62bfb..3b612682f 100644 --- a/integrations/browser-js/package.json +++ b/integrations/browser-js/package.json @@ -25,11 +25,10 @@ }, "dependencies": { "als-browser": "^1.0.1", - "braintrust": ">=3.0.0-rc.29" + "braintrust": "workspace:^" }, "devDependencies": { "@types/node": "^20.10.5", - "braintrust": "workspace:*", "tsup": "^8.5.1", "typescript": "^5.3.3", "vitest": "4.1.5" diff --git a/js/package.json b/js/package.json index 21d1af255..8d02f865b 100644 --- a/js/package.json +++ b/js/package.json @@ -200,8 +200,6 @@ "@vercel/functions": "^1.0.2", "ajv": "^8.20.0", "argparse": "^2.0.1", - "boxen": "^8.0.1", - "chalk": "^4.1.2", "cli-progress": "^3.12.0", "cli-table3": "^0.6.5", "cors": "^2.8.5", diff --git a/js/src/cli/index.ts b/js/src/cli/index.ts index fcfb0fefc..cf41c1fb9 100755 --- a/js/src/cli/index.ts +++ b/js/src/cli/index.ts @@ -5,7 +5,7 @@ import * as dotenv from "dotenv"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import util from "node:util"; +import util, { styleText } from "node:util"; import * as fsWalk from "@nodelib/fs.walk"; import { minimatch } from "minimatch"; import { ArgumentParser } from "argparse"; @@ -25,7 +25,6 @@ import { BarProgressReporter, SimpleProgressReporter, } from "./reporters/progress"; -import chalk from "chalk"; import { terminalLink } from "termi-link"; // Re-use the module resolution logic from Jest @@ -129,8 +128,8 @@ async function initExperiment( : "locally"; // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. console.error( - chalk.cyan("▶") + - ` Experiment ${chalk.bold(info.experimentName)} is running at ${linkText}`, + styleText("cyan", "▶") + + ` Experiment ${styleText("bold", info.experimentName)} is running at ${linkText}`, ); return logger; } @@ -596,8 +595,9 @@ async function runOnce( // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. console.error( - chalk.dim( - `Processing ${chalk.bold(resultPromises.length)} evaluator${resultPromises.length === 1 ? "" : "s"}...`, + styleText( + "dim", + `Processing ${styleText("bold", String(resultPromises.length))} evaluator${resultPromises.length === 1 ? "" : "s"}...`, ), ); const allEvalsResults = await Promise.all(resultPromises); diff --git a/js/src/cli/reporters/eval.ts b/js/src/cli/reporters/eval.ts index 5093bd464..8f7ff8708 100644 --- a/js/src/cli/reporters/eval.ts +++ b/js/src/cli/reporters/eval.ts @@ -1,6 +1,5 @@ -import chalk from "chalk"; +import { stripVTControlCharacters, styleText } from "node:util"; import { terminalLink } from "termi-link"; -import boxen from "boxen"; import Table from "cli-table3"; import pluralize from "pluralize"; @@ -9,10 +8,45 @@ import type { ReporterDef } from "../../reporters/types"; import { EvaluatorDef, EvalResultWithSummary } from "../../framework"; import { isEmpty } from "../../util"; +function visibleLength(text: string) { + return stripVTControlCharacters(text).length; +} + +function padEndVisible(text: string, targetLength: number) { + return text + " ".repeat(Math.max(0, targetLength - visibleLength(text))); +} + +function formatSummaryBox(content: string) { + const title = styleText("gray", " Experiment summary "); + const lines = content.split("\n"); + const contentWidth = Math.max( + visibleLength(title), + ...lines.map((line) => visibleLength(line) + 2), + ); + + const horizontal = "─"; + const top = + styleText("gray", "╭") + + title + + styleText( + "gray", + horizontal.repeat(contentWidth - visibleLength(title)) + "╮", + ); + const body = lines + .map( + (line) => + `${styleText("gray", "│")} ${padEndVisible(line, contentWidth - 2)} ${styleText("gray", "│")}`, + ) + .join("\n"); + const bottom = styleText("gray", "╰" + horizontal.repeat(contentWidth) + "╯"); + + return top + "\n" + body + "\n" + bottom; +} + function formatExperimentSummaryFancy(summary: ExperimentSummary) { let comparisonLine = ""; if (summary.comparisonExperimentName) { - comparisonLine = `${summary.comparisonExperimentName} ${chalk.gray("(baseline)")} ← ${summary.experimentName} ${chalk.gray("(comparison)")}\n\n`; + comparisonLine = `${summary.comparisonExperimentName} ${styleText("gray", "(baseline)")} ← ${summary.experimentName} ${styleText("gray", "(comparison)")}\n\n`; } const tableParts: string[] = []; @@ -22,13 +56,13 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) { const hasComparison = !!summary.comparisonExperimentName; if (hasScores || hasMetrics) { - const headers = [chalk.gray("Name"), chalk.gray("Value")]; + const headers = [styleText("gray", "Name"), styleText("gray", "Value")]; if (hasComparison) { headers.push( - chalk.gray("Change"), - chalk.gray("Improvements"), - chalk.gray("Regressions"), + styleText("gray", "Change"), + styleText("gray", "Improvements"), + styleText("gray", "Regressions"), ); } @@ -62,28 +96,28 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) { const scoreValues: ScoreSummary[] = Object.values(summary.scores); for (const score of scoreValues) { const scorePercent = (score.score * 100).toFixed(2); - const scoreValue = chalk.white(`${scorePercent}%`); + const scoreValue = styleText("white", `${scorePercent}%`); let diffString = ""; if (!isEmpty(score.diff)) { const diffPercent = (score.diff! * 100).toFixed(2); const diffSign = score.diff! > 0 ? "+" : ""; - const diffColor = score.diff! > 0 ? chalk.green : chalk.red; - diffString = diffColor(`${diffSign}${diffPercent}%`); + const diffColor = score.diff! > 0 ? "green" : "red"; + diffString = styleText(diffColor, `${diffSign}${diffPercent}%`); } else { - diffString = chalk.gray("-"); + diffString = styleText("gray", "-"); } const improvements = score.improvements > 0 - ? chalk.dim.green(score.improvements) - : chalk.gray("-"); + ? styleText(["dim", "green"], String(score.improvements)) + : styleText("gray", "-"); const regressions = score.regressions > 0 - ? chalk.dim.red(score.regressions) - : chalk.gray("-"); + ? styleText(["dim", "red"], String(score.regressions)) + : styleText("gray", "-"); - const row = [`${chalk.blue("◯")} ${score.name}`, scoreValue]; + const row = [`${styleText("blue", "◯")} ${score.name}`, scoreValue]; if (hasComparison) { row.push(diffString, improvements, regressions); } @@ -94,7 +128,8 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) { for (const metric of metricValues) { const fractionDigits = Number.isInteger(metric.metric) ? 0 : 2; const formattedValue = metric.metric.toFixed(fractionDigits); - const metricValue = chalk.white( + const metricValue = styleText( + "white", metric.unit === "$" ? `${metric.unit}${formattedValue}` : `${formattedValue}${metric.unit}`, @@ -104,22 +139,22 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) { if (!isEmpty(metric.diff)) { const diffPercent = (metric.diff! * 100).toFixed(2); const diffSign = metric.diff! > 0 ? "+" : ""; - const diffColor = metric.diff! > 0 ? chalk.green : chalk.red; - diffString = diffColor(`${diffSign}${diffPercent}%`); + const diffColor = metric.diff! > 0 ? "green" : "red"; + diffString = styleText(diffColor, `${diffSign}${diffPercent}%`); } else { - diffString = chalk.gray("-"); + diffString = styleText("gray", "-"); } const improvements = metric.improvements > 0 - ? chalk.dim.green(metric.improvements) - : chalk.gray("-"); + ? styleText(["dim", "green"], String(metric.improvements)) + : styleText("gray", "-"); const regressions = metric.regressions > 0 - ? chalk.dim.red(metric.regressions) - : chalk.gray("-"); + ? styleText(["dim", "red"], String(metric.regressions)) + : styleText("gray", "-"); - const row = [`${chalk.magenta("◯")} ${metric.name}`, metricValue]; + const row = [`${styleText("magenta", "◯")} ${metric.name}`, metricValue]; if (hasComparison) { row.push(diffString, improvements, regressions); } @@ -141,23 +176,10 @@ function formatExperimentSummaryFancy(summary: ExperimentSummary) { const boxContent = [content, footer].filter(Boolean).join("\n\n"); - try { - return ( - "\n" + - boxen(boxContent, { - title: chalk.gray("Experiment summary"), - titleAlignment: "left", - padding: 0.5, - borderColor: "gray", - borderStyle: "round", - }) - ); - } catch { - return "\n" + chalk.gray("Experiment summary") + "\n" + boxContent + "\n"; - } + return "\n" + formatSummaryBox(boxContent); } -export const warning = chalk.yellow; +export const warning = (text: string) => styleText("yellow", text); export const fancyReporter: ReporterDef = { name: "Braintrust fancy reporter", diff --git a/js/src/cli/reporters/progress.ts b/js/src/cli/reporters/progress.ts index c9eebc113..e764709f3 100644 --- a/js/src/cli/reporters/progress.ts +++ b/js/src/cli/reporters/progress.ts @@ -1,4 +1,4 @@ -import chalk from "chalk"; +import { styleText } from "node:util"; import * as cliProgress from "cli-progress"; import type { ProgressReporter } from "../../reporters/types"; @@ -22,7 +22,7 @@ export class BarProgressReporter implements ProgressReporter { constructor() { this.multiBar = new cliProgress.MultiBar( { - format: `${chalk.blueBright("{bar}")} ${chalk.blue("{evaluator}")} {percentage}% ${chalk.gray("{value}/{total} {eta_formatted}")}`, + format: `${styleText("blueBright", "{bar}")} ${styleText("blue", "{evaluator}")} {percentage}% ${styleText("gray", "{value}/{total} {eta_formatted}")}`, hideCursor: true, barsize: 10, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16c0024c0..ee1ef81ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,8 +117,8 @@ importers: specifier: ^1.0.1 version: 1.0.1 braintrust: - specifier: '>=3.0.0-rc.29' - version: 3.0.0-rc.29(zod@3.25.76) + specifier: workspace:^ + version: link:../../js zod: specifier: ^3.25.34 || ^4.0 version: 3.25.76 @@ -339,12 +339,6 @@ importers: argparse: specifier: ^2.0.1 version: 2.0.1 - boxen: - specifier: ^8.0.1 - version: 8.0.1 - chalk: - specifier: ^4.1.2 - version: 4.1.2 cli-progress: specifier: ^3.12.0 version: 3.12.0 @@ -623,10 +617,6 @@ packages: resolution: {integrity: sha512-VTipPQ92Moa5Ovg/nZIc8yNoIFfukZjUHZcQMduJbiUh3CLQyrBAKTEV9AwjPy8wgVxj3+GZjon0yyOJKhfp5g==} engines: {node: '>=18'} - '@ai-sdk/provider@1.1.3': - resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} - engines: {node: '>=18'} - '@ai-sdk/provider@2.0.0': resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} @@ -2612,9 +2602,6 @@ packages: als-browser@1.0.1: resolution: {integrity: sha512-DjavKf6zf4DFPdEmgsEM474MBjFcZG/1amv2/+WHGf61kVQWqf7XEn4jvpjFS4ssQbh/pkmYThaPfQK1ERC+3g==} - ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -2745,10 +2732,6 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - boxen@8.0.1: - resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} - engines: {node: '>=18'} - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2763,12 +2746,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - braintrust@3.0.0-rc.29: - resolution: {integrity: sha512-MTY0hCsBlRZcpYUf+WYeQOFcKK6Wto9P71q5UktAhIna+Gr6ZDPfp7jKIl6Mv9O8cD+PKBYBvL7pPwNTIfLkeA==} - hasBin: true - peerDependencies: - zod: ^3.25.34 || ^4.0 - browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2827,10 +2804,6 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - camelcase@8.0.0: - resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} - engines: {node: '>=16'} - caniuse-lite@1.0.30001764: resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} @@ -2842,10 +2815,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -2875,10 +2844,6 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -5594,10 +5559,6 @@ packages: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} - type-fest@4.30.0: - resolution: {integrity: sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==} - engines: {node: '>=16'} - type-fest@5.6.0: resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} engines: {node: '>=20'} @@ -5896,10 +5857,6 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - widest-line@5.0.0: - resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} - engines: {node: '>=18'} - winston-transport@4.9.0: resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} engines: {node: '>= 12.0.0'} @@ -6071,10 +6028,6 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/provider@1.1.3': - dependencies: - json-schema: 0.4.0 - '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 @@ -7328,7 +7281,7 @@ snapshots: pkce-challenge: 5.0.0 raw-body: 3.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod-to-json-schema: 3.25.1(zod@3.25.76) transitivePeerDependencies: - supports-color optional: true @@ -8534,10 +8487,6 @@ snapshots: als-browser@1.0.1: {} - ansi-align@3.0.1: - dependencies: - string-width: 4.2.3 - ansi-colors@4.1.3: {} ansi-escapes@7.3.0: @@ -8684,17 +8633,6 @@ snapshots: transitivePeerDependencies: - supports-color - boxen@8.0.1: - dependencies: - ansi-align: 3.0.1 - camelcase: 8.0.0 - chalk: 5.3.0 - cli-boxes: 3.0.0 - string-width: 7.2.0 - type-fest: 4.30.0 - widest-line: 5.0.0 - wrap-ansi: 9.0.2 - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -8712,35 +8650,6 @@ snapshots: dependencies: fill-range: 7.1.1 - braintrust@3.0.0-rc.29(zod@3.25.76): - dependencies: - '@ai-sdk/provider': 1.1.3 - '@next/env': 14.2.3 - '@vercel/functions': 1.0.2 - argparse: 2.0.1 - boxen: 8.0.1 - chalk: 4.1.2 - cli-progress: 3.12.0 - cli-table3: 0.6.5 - cors: 2.8.5 - dotenv: 16.4.5 - esbuild: 0.27.4 - eventsource-parser: 1.1.2 - express: 4.22.1 - graceful-fs: 4.2.11 - http-errors: 2.0.1 - minimatch: 9.0.9 - mustache: 4.2.0 - pluralize: 8.0.0 - simple-git: 3.36.0 - source-map: 0.7.6 - termi-link: 1.1.0 - uuid: 9.0.1 - zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) - transitivePeerDependencies: - - supports-color - browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.14 @@ -8816,8 +8725,6 @@ snapshots: camelcase@6.3.0: {} - camelcase@8.0.0: {} - caniuse-lite@1.0.30001764: {} chai@6.2.2: {} @@ -8827,8 +8734,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.3.0: {} - chardet@2.1.1: {} cheminfo-types@1.8.1: {} @@ -8848,8 +8753,6 @@ snapshots: clean-stack@2.2.0: optional: true - cli-boxes@3.0.0: {} - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -11923,8 +11826,6 @@ snapshots: type-fest@3.13.1: {} - type-fest@4.30.0: {} - type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 @@ -12358,10 +12259,6 @@ snapshots: string-width: 4.2.3 optional: true - widest-line@5.0.0: - dependencies: - string-width: 7.2.0 - winston-transport@4.9.0: dependencies: logform: 2.7.0