#!/usr/bin/env node import fs from "node:fs"; import process from "node:process"; import { spawn } from "@lydell/node-pty"; import { readPositiveIntEnv } from "./env-limits.mjs"; const [logPath, command, ...args] = process.argv.slice(2); const OUTPUT_MAX_BYTES = readPositiveIntEnv("OPENCLAW_E2E_PTY_OUTPUT_MAX_BYTES", 16 * 1024 * 1024); if (!logPath || !command) { console.error("usage: run-with-pty.mjs [args...]"); process.exit(2); } const log = fs.createWriteStream(logPath, { flags: "w" }); const pty = spawn(command, args, { name: process.env.TERM || "xterm-256color", cols: readPositiveIntEnv("COLUMNS", 120), rows: readPositiveIntEnv("LINES", 40), cwd: process.cwd(), env: process.env, }); let exiting = false; const outputLimitMarker = `\n[run-with-pty output truncated after ${OUTPUT_MAX_BYTES} bytes]\n`; const outputState = { bytes: 0, truncated: false, }; function writeCappedOutput(data) { if (outputState.truncated) { return; } const buffer = Buffer.from(data); const remainingBytes = OUTPUT_MAX_BYTES - outputState.bytes; if (buffer.byteLength <= remainingBytes) { outputState.bytes += buffer.byteLength; log.write(buffer); process.stdout.write(buffer); return; } if (remainingBytes > 0) { const head = buffer.subarray(0, remainingBytes); log.write(head); process.stdout.write(head); } outputState.bytes = OUTPUT_MAX_BYTES; outputState.truncated = true; log.write(outputLimitMarker); process.stdout.write(outputLimitMarker); } pty.onData((data) => { writeCappedOutput(data); }); pty.onExit(({ exitCode, signal }) => { exiting = true; log.end(() => { if (typeof exitCode === "number") { process.exit(exitCode); } process.exit(signal ? 128 + signal : 1); }); }); process.stdin.on("data", (chunk) => { pty.write(chunk.toString("utf8")); }); for (const signal of ["SIGINT", "SIGTERM"]) { process.on(signal, () => { if (!exiting) { pty.kill(signal); } }); }