@clawdcc/cvm-benchmark
Version:
Comprehensive benchmarking and performance analysis tools for Claude Code versions
203 lines (199 loc) • 6.67 kB
JavaScript
// src/benchmarks/interactive-pty.ts
import * as pty from "node-pty";
// src/utils/logger.ts
import chalk from "chalk";
var Logger = class {
level = "info";
silent = false;
setLevel(level) {
this.level = level;
}
setSilent(silent) {
this.silent = silent;
}
debug(message, ...args) {
if (!this.silent && this.shouldLog("debug")) {
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
}
}
info(message, ...args) {
if (!this.silent && this.shouldLog("info")) {
console.log(chalk.blue(`\u2139 ${message}`), ...args);
}
}
warn(message, ...args) {
if (!this.silent && this.shouldLog("warn")) {
console.warn(chalk.yellow(`\u26A0 ${message}`), ...args);
}
}
error(message, ...args) {
if (!this.silent && this.shouldLog("error")) {
console.error(chalk.red(`\u2716 ${message}`), ...args);
}
}
success(message, ...args) {
if (!this.silent && this.shouldLog("success")) {
console.log(chalk.green(`\u2713 ${message}`), ...args);
}
}
shouldLog(level) {
const levels = ["debug", "info", "warn", "error", "success"];
return levels.indexOf(level) >= levels.indexOf(this.level);
}
};
var logger = new Logger();
// src/benchmarks/interactive-pty.ts
async function benchmarkInteractive(options) {
const { claudePath: claudePath2, cwd: cwd2, timeout: timeout2 = 3e4 } = options;
return new Promise((resolve) => {
const startTime = Date.now();
let output = "";
const signals = {
bracketedPaste: false,
focusEvents: false,
prompt: false
};
let readyDetected = false;
let errorDetected = false;
let sessionId = void 0;
let trustPromptHandled = false;
let ptyProcess;
try {
ptyProcess = pty.spawn(claudePath2, [], {
name: "xterm-256color",
cols: 80,
rows: 30,
cwd: cwd2,
env: process.env
});
} catch (error) {
resolve({
time: Date.now() - startTime,
result: "failed",
reason: `PTY spawn failed: ${error instanceof Error ? error.message : String(error)}`,
signals,
sessionId
});
return;
}
const timeoutId = setTimeout(() => {
ptyProcess.kill();
resolve({
time: Date.now() - startTime,
result: "timeout",
reason: `Benchmark timed out after ${timeout2}ms`,
signals,
sessionId
});
}, timeout2);
ptyProcess.onData((data) => {
output += data;
if (!trustPromptHandled && output.includes("Do you trust the files")) {
trustPromptHandled = true;
logger.debug("Trust prompt detected, auto-accepting...");
setTimeout(() => ptyProcess.write("\r"), 100);
}
if (!sessionId) {
const sessionMatch = output.match(/"session_id":"([a-f0-9-]+)"/);
if (sessionMatch) {
sessionId = sessionMatch[1];
}
}
if ((output.includes("needs update") || output.includes("newer version") || output.includes("requires") || output.includes("minimum version")) && !errorDetected) {
errorDetected = true;
const cleanOutput = output.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
const lines = cleanOutput.split("\n");
const errorStartIdx = lines.findIndex(
(l) => l.includes("needs update") || l.includes("newer version") || l.includes("requires") || l.includes("minimum version")
);
const errorLines = lines.slice(Math.max(0, errorStartIdx - 1), errorStartIdx + 6);
const fullErrorMessage = errorLines.join("\n").trim();
const versionMatch = cleanOutput.match(/(\d+\.\d+\.\d+)\s+or higher/i) || cleanOutput.match(/version\s+\((\d+\.\d+\.\d+)/i) || cleanOutput.match(/requires\s+(\d+\.\d+\.\d+)/i) || cleanOutput.match(/minimum\s+version[:\s]+(\d+\.\d+\.\d+)/i) || cleanOutput.match(/v?(\d+\.\d+\.\d+)\+/);
const minVersion = versionMatch ? versionMatch[1] : null;
const EXPECTED_MIN_VERSION = "1.0.24";
if (minVersion && minVersion !== EXPECTED_MIN_VERSION) {
logger.warn(`Minimum version changed: expected ${EXPECTED_MIN_VERSION}, found ${minVersion}`);
}
clearTimeout(timeoutId);
ptyProcess.kill();
resolve({
time: Date.now() - startTime,
result: "error_detected",
reason: "version_requirement_not_met",
minVersionRequired: minVersion || EXPECTED_MIN_VERSION,
errorMessage: fullErrorMessage,
rawOutput: cleanOutput.substring(0, 2e3),
sessionId
});
return;
}
if (data.includes("\x1B[?2004h") && !signals.bracketedPaste) {
signals.bracketedPaste = true;
}
if (data.includes("\x1B[?1004h") && !signals.focusEvents) {
signals.focusEvents = true;
}
const stripped = data.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
if (/>\s/.test(stripped) && !signals.prompt) {
signals.prompt = true;
}
if (signals.bracketedPaste && signals.focusEvents && signals.prompt && !readyDetected) {
readyDetected = true;
setTimeout(() => {
clearTimeout(timeoutId);
ptyProcess.kill();
resolve({
time: Date.now() - startTime,
result: "ready",
reason: "all terminal signals received and process stable",
signals: { ...signals },
sessionId
});
}, 500);
}
});
ptyProcess.onExit(({ exitCode }) => {
if (errorDetected || readyDetected) return;
clearTimeout(timeoutId);
const elapsed = Date.now() - startTime;
if (signals.prompt) {
resolve({
time: elapsed,
result: "ui_then_exit",
reason: "showed prompt but immediately exited",
signals: { ...signals },
exitCode,
sessionId
});
return;
}
resolve({
time: elapsed,
result: "exited_early",
reason: "process exited before showing prompt",
signals: { ...signals },
exitCode,
sessionId
});
});
});
}
// src/benchmarks/interactive-worker.ts
var [claudePath, cwd, timeout] = process.argv.slice(2);
if (!claudePath || !cwd) {
console.error("Usage: interactive-worker.js <claudePath> <cwd> [timeout]");
process.exit(1);
}
benchmarkInteractive({
claudePath,
cwd,
timeout: timeout ? parseInt(timeout) : 3e4
}).then((result) => {
console.log(JSON.stringify(result));
process.exit(0);
}).catch((error) => {
console.error("Benchmark error:", error);
process.exit(1);
});
//# sourceMappingURL=interactive-worker.js.map