#!/usr/bin/env node // HWLAB #803 noise-capture CLI. // // Captures the rendered Agent workspace (#code-agent-summary, per-message // meta/pending/runtime/session/trace cards) from a live or local Cloud Web // build and prints a structured JSON dump that can be diffed before/after a // Web UI refactor. // // Usage: // CHROMIUM_PATH=/usr/bin/chromium-browser \ // node tools/capture-issue-803-noise.mjs \ // --url http://74.48.78.17:17666/ --user admin --pass hwlab2026 \ // --png /tmp/hwlab-noise.png // CHROMIUM_PATH=/usr/bin/chromium-browser \ // NOISE_USER=admin NOISE_PASS=hwlab2026 \ // node tools/capture-issue-803-noise.mjs import { setTimeout as sleep } from "node:timers/promises"; import { importPlaywright, launchChromium } from "../scripts/src/browser-launcher.mjs"; function parseArgs(argv) { const args = { url: process.env.NOISE_URL ?? "http://74.48.78.17:17666/", user: process.env.NOISE_USER ?? "admin", pass: process.env.NOISE_PASS ?? "hwlab2026", message: process.env.NOISE_MESSAGE ?? null, viewport: process.env.NOISE_VIEWPORT ?? "1440x900", png: process.env.NOISE_PNG ?? "/tmp/hwlab-noise.png" }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--url") args.url = argv[++index]; else if (arg === "--user") args.user = argv[++index]; else if (arg === "--pass") args.pass = argv[++index]; else if (arg === "--message") args.message = argv[++index]; else if (arg === "--png") args.png = argv[++index]; else if (arg === "--viewport") args.viewport = argv[++index]; } const [w, h] = args.viewport.split("x").map(Number); args.viewportWidth = Number.isFinite(w) ? w : 1440; args.viewportHeight = Number.isFinite(h) ? h : 900; return args; } const args = parseArgs(process.argv.slice(2)); const { chromium } = await importPlaywright(); const browser = await launchChromium(chromium); console.error(`[noise] browser ${JSON.stringify(browser.hwlabLaunchPlan ?? {})}`); const ctx = await browser.newContext({ viewport: { width: args.viewportWidth, height: args.viewportHeight } }); const page = await ctx.newPage(); await page.goto(args.url, { waitUntil: "networkidle", timeout: 30000 }); try { await page.locator("#login-username").fill(args.user, { timeout: 5000 }); await page.locator("#login-password").fill(args.pass); await page.locator("#login-submit").click(); await page.locator("#command-input").waitFor({ state: "visible", timeout: 12000 }); } catch (error) { console.error("[noise] login failed:", error instanceof Error ? error.message : String(error)); } await page.waitForTimeout(1500); if (args.message) { try { await page.locator("#command-input").fill(args.message, { timeout: 5000 }); await page.locator("#command-send").click(); await sleep(8000); } catch (error) { console.error("[noise] submit failed:", error instanceof Error ? error.message : String(error)); } } const data = await page.evaluate(() => { const summary = document.querySelector("#code-agent-summary"); const summaryRect = summary?.getBoundingClientRect(); const summaryText = summary?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 800) ?? null; const summaryDetail = document.querySelector("#code-agent-summary-detail"); const summaryDetailRect = summaryDetail?.getBoundingClientRect(); const summaryDetailText = summaryDetail?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 1500) ?? null; const summaryIsOpen = summary ? summary.open === true : null; const summaryDebugBtn = document.querySelector("#code-agent-debug-trigger"); const convList = document.querySelector("#conversation-list"); const convListRect = convList?.getBoundingClientRect(); const messages = [...document.querySelectorAll(".message-card")].map((card) => { const rect = card.getBoundingClientRect(); const role = card.getAttribute("data-message-role") ?? [...card.classList].find((cls) => cls.startsWith("message-"))?.replace(/^message-/, "") ?? null; const meta = card.querySelector(".message-meta")?.textContent?.replace(/\s+/gu, " ").trim() ?? null; const pending = card.querySelector(".message-pending-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null; const session = card.querySelector(".message-session-context")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null; const runtime = card.querySelector(".message-runtime-path")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 600) ?? null; const trace = card.querySelector(".message-trace")?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 400) ?? null; const traceEventsRect = card.querySelector(".message-trace-events")?.getBoundingClientRect(); const debugTrigger = card.querySelector(".debug-dialog-trigger"); const cardWidth = rect.width; const tracePct = cardWidth > 0 && traceEventsRect ? Math.round((traceEventsRect.width / cardWidth) * 100) : null; return { role, meta, pending, session, runtime, trace, debugTriggerText: debugTrigger?.textContent ?? null, cardWidth: Math.round(cardWidth), traceEventsWidth: traceEventsRect ? Math.round(traceEventsRect.width) : null, traceEventsPct: tracePct }; }); const systemCount = messages.filter((m) => m.role === "system").length; const roleCounts = messages.reduce((acc, m) => { const key = m.role ?? "unknown"; acc[key] = (acc[key] ?? 0) + 1; return acc; }, {}); return { summaryText, summaryDetailText, summaryIsOpen, summaryDebugBtnExists: !!summaryDebugBtn, summaryRect: summaryRect ? { width: Math.round(summaryRect.width), height: Math.round(summaryRect.height) } : null, summaryDetailRect: summaryDetailRect ? { width: Math.round(summaryDetailRect.width), height: Math.round(summaryDetailRect.height) } : null, convListRect: convListRect ? { width: Math.round(convListRect.width), height: Math.round(convListRect.height) } : null, messageCount: messages.length, systemMessageCount: systemCount, roleCounts, messages }; }); console.log(JSON.stringify(data, null, 2)); await page.screenshot({ path: args.png, fullPage: false }); await browser.close();