Initial commit
This commit is contained in:
114
skills/browser-tools/scripts/evaluate.js
Executable file
114
skills/browser-tools/scripts/evaluate.js
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import puppeteer from "puppeteer-core";
|
||||
|
||||
import {
|
||||
DEFAULT_PORT,
|
||||
createLogger,
|
||||
fail,
|
||||
getActivePage,
|
||||
parseArgs,
|
||||
printJSON,
|
||||
resolveBrowserConnection,
|
||||
normalizeNumber,
|
||||
} from "./config.js";
|
||||
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
boolean: ["json", "quiet"],
|
||||
string: ["file", "ws", "host"],
|
||||
number: ["port", "timeout", "truncate"],
|
||||
alias: {
|
||||
j: "json",
|
||||
q: "quiet",
|
||||
},
|
||||
defaults: {
|
||||
port: DEFAULT_PORT,
|
||||
timeout: 30000,
|
||||
truncate: 8000,
|
||||
},
|
||||
});
|
||||
|
||||
const jsonOutput = Boolean(args.json);
|
||||
const logger = createLogger({ quiet: Boolean(args.quiet), json: jsonOutput });
|
||||
|
||||
const expression = await resolveExpression(args);
|
||||
|
||||
if (!expression) {
|
||||
fail("No JavaScript provided. Pass an expression or use --file/STDIN.", { json: jsonOutput });
|
||||
}
|
||||
|
||||
const timeout = normalizeNumber(args.timeout, 30000);
|
||||
const port = normalizeNumber(args.port, DEFAULT_PORT);
|
||||
|
||||
const connectionOptions = resolveBrowserConnection({ port, host: args.host, ws: args.ws });
|
||||
|
||||
let browser;
|
||||
try {
|
||||
browser = await puppeteer.connect({
|
||||
...connectionOptions,
|
||||
timeout,
|
||||
});
|
||||
} catch (error) {
|
||||
fail(`Failed to connect to browser: ${error.message}`, { json: jsonOutput });
|
||||
}
|
||||
|
||||
try {
|
||||
const page = await getActivePage(browser, { index: -1 });
|
||||
if (!page) {
|
||||
fail("No active page found. Navigate to a page first.", { json: jsonOutput });
|
||||
}
|
||||
|
||||
const result = await page.evaluate(async (code) => {
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||
const fn = new AsyncFunction(`return (${code})`);
|
||||
return await fn();
|
||||
}, expression);
|
||||
|
||||
const truncated = truncateResult(result, normalizeNumber(args.truncate, 8000));
|
||||
|
||||
if (jsonOutput) {
|
||||
printJSON({ ok: true, result });
|
||||
} else {
|
||||
process.stdout.write(`${truncated}\n`);
|
||||
logger.info(truncated);
|
||||
}
|
||||
} catch (error) {
|
||||
fail(`Evaluation failed: ${error.message}`, { json: jsonOutput });
|
||||
} finally {
|
||||
if (browser) await browser.disconnect();
|
||||
}
|
||||
|
||||
async function resolveExpression(parsed) {
|
||||
if (parsed.file) {
|
||||
const content = await readFile(parsed.file, "utf8");
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
if (parsed._.length > 0) {
|
||||
return parsed._.join(" ");
|
||||
}
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
const chunks = [];
|
||||
for await (const chunk of process.stdin) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8").trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function truncateResult(value, maxLength) {
|
||||
if (value === undefined) return "undefined";
|
||||
if (value === null) return "null";
|
||||
|
||||
if (typeof value === "string") {
|
||||
return value.length > maxLength ? `${value.slice(0, maxLength)}…` : value;
|
||||
}
|
||||
|
||||
const serialized = JSON.stringify(value, null, 2);
|
||||
if (!serialized) return String(value);
|
||||
return serialized.length > maxLength ? `${serialized.slice(0, maxLength)}…` : serialized;
|
||||
}
|
||||
Reference in New Issue
Block a user