#!/usr/bin/env node /** * lx.js — 布局紧凑渲染器(Layout eXtract) * * 把 MCP `device_get_layout` 的 JSON 结果渲染成一行一个控件的紧凑列表, * 避免把 25KB 的原始 JSON 直接塞进上下文。 * * 用法: * node _probe/lx.js [文案过滤词] * node _probe/lx.js --all # 含 dropped / scrollables 统计 * * 输入既可以是工具返回的原样 JSON,也可以是 {"content":[{"text":""}]} 形态的 MCP 响应。 */ 'use strict'; const fs = require('fs'); function readPayload(file) { const raw = fs.readFileSync(file, 'utf8'); const parsed = JSON.parse(raw); if (Array.isArray(parsed.content) && parsed.content[0] && parsed.content[0].text) { return readPayloadValue(JSON.parse(parsed.content[0].text)); } return readPayloadValue(parsed); } /** 工具返回可能把布局挂在 tree 字段下,这里统一成 {nodes, dropped, scrollables} */ function readPayloadValue(value) { if (value && value.tree && Array.isArray(value.tree.nodes)) { return { device: value.device, ...value.tree }; } return value; } function shorten(value, max = 60) { const text = String(value == null ? '' : value).replace(/\s+/g, ' '); return text.length > max ? `${text.slice(0, max - 1)}…` : text; } function renderNode(node) { const flags = [ node.clk ? 'clk' : '-', node.sc ? 'scr' : '-', node.ed ? 'ed' : '-', node.opaque ? 'shell' : '-', ].join(' '); const label = shorten(node.text || node.desc || ''); const parent = node.pt ? ` <-${shorten(node.pt, 18)}` : ''; return [ String(node.ref).padEnd(12), String(node.cls || '').padEnd(16), flags, `(${node.cx},${node.cy})`.padEnd(12), `[${(node.b || []).join(',')}]`.padEnd(26), label + parent, ].join(' '); } function main() { const [file, filter] = process.argv.slice(2); const layout = readPayload(file); const nodes = layout.nodes || []; const shown = filter && filter !== '--all' ? nodes.filter((n) => (String(n.text || '') + String(n.desc || '')).includes(filter)) : nodes; console.log(`# 节点 ${nodes.length} → 显示 ${shown.length}${layout.truncated ? ' (truncated)' : ''}`); shown.forEach((node) => console.log(renderNode(node))); if (layout.scrollables && layout.scrollables.length) { console.log('\n# scrollables'); layout.scrollables.forEach((s) => { console.log(` ${s.ref} dir=${s.dir} [${(s.b || []).join(',')}]`); }); } if (layout.dropped && (filter === '--all' || !filter)) { console.log(`\n# dropped ${JSON.stringify(layout.dropped)}`); } } main();