@mearl/client
Advanced tools
| export interface SuggestableCommand { | ||
| readonly aliases?: readonly string[]; | ||
| readonly description: string; | ||
| readonly examples?: readonly string[]; | ||
| readonly name: string; | ||
| } | ||
| export interface CommandSuggestion { | ||
| command: SuggestableCommand; | ||
| score: number; | ||
| } | ||
| export declare function commandInput(args: readonly string[]): string; | ||
| export declare function findCommandSuggestions(args: readonly string[], commands: readonly SuggestableCommand[], limit?: number): CommandSuggestion[]; | ||
| export declare function formatCommandSuggestions(commandName: string, suggestions: readonly CommandSuggestion[]): string[]; |
| import { distance } from 'fastest-levenshtein'; | ||
| const MIN_SIMILARITY = 0.5; | ||
| const MAX_SCORE_GAP = 0.1; | ||
| function normalizeCommand(value) { | ||
| return value | ||
| .normalize('NFKC') | ||
| .toLowerCase() | ||
| .replace(/[^a-z0-9]+/g, '_') | ||
| .replace(/^_+|_+$/g, ''); | ||
| } | ||
| function leadingCommandParts(args) { | ||
| const parts = []; | ||
| for (const arg of args) { | ||
| if (arg.startsWith('-')) | ||
| break; | ||
| parts.push(arg); | ||
| } | ||
| return parts; | ||
| } | ||
| function commandQueries(args) { | ||
| const parts = leadingCommandParts(args); | ||
| const queries = new Set(); | ||
| for (let length = parts.length; length > 0; length -= 1) { | ||
| const query = normalizeCommand(parts.slice(0, length).join('_')); | ||
| if (query) | ||
| queries.add(query); | ||
| } | ||
| return [...queries]; | ||
| } | ||
| function similarity(left, right) { | ||
| const length = Math.max(left.length, right.length); | ||
| return length === 0 ? 0 : (length - distance(left, right)) / length; | ||
| } | ||
| export function commandInput(args) { | ||
| return leadingCommandParts(args).join(' '); | ||
| } | ||
| export function findCommandSuggestions(args, commands, limit = 3) { | ||
| const queries = commandQueries(args); | ||
| if (queries.length === 0 || limit <= 0) | ||
| return []; | ||
| const ranked = commands | ||
| .map(command => { | ||
| const keys = [command.name, ...(command.aliases ?? [])].map(normalizeCommand); | ||
| const score = Math.max(...queries.flatMap(query => keys.map(key => similarity(query, key)))); | ||
| return { command, score }; | ||
| }) | ||
| .filter(suggestion => suggestion.score >= MIN_SIMILARITY) | ||
| .sort((left, right) => right.score - left.score || left.command.name.localeCompare(right.command.name)); | ||
| const bestScore = ranked[0]?.score; | ||
| if (bestScore === undefined) | ||
| return []; | ||
| return ranked.filter(suggestion => bestScore - suggestion.score <= MAX_SCORE_GAP).slice(0, limit); | ||
| } | ||
| export function formatCommandSuggestions(commandName, suggestions) { | ||
| return suggestions.map(({ command }) => { | ||
| const invocation = command.examples?.[0] ?? command.name; | ||
| return ` ${commandName} ${invocation}\n ${command.description}`; | ||
| }); | ||
| } |
| import { PAGE_ACT_ACTIONS } from './generated-browser-action-protocol.js'; | ||
| const SEND_REQUEST_FILE_TRANSFER_TIMEOUT_MS = 120_000; | ||
| // AgentBay staging can spend up to 60s transferring bytes and then 120s syncing the session disk. | ||
| const PAGE_UPLOAD_FILE_TRANSFER_TIMEOUT_MS = 180_000; | ||
| // AgentBay context sync can poll for about 12 minutes before session deletion waits up to 5 more. | ||
@@ -59,2 +61,6 @@ const BROWSER_CLOSE_TIMEOUT_SEC = 18 * 60; | ||
| : data.observe !== false; | ||
| if (action === 'page_upload') { | ||
| const actionTimeoutSec = observationEnabled ? resolveObservedActionTimeoutSec(data) : fallback; | ||
| return Math.max(fallback, actionTimeoutSec + Math.ceil(PAGE_UPLOAD_FILE_TRANSFER_TIMEOUT_MS / 1000)); | ||
| } | ||
| if (observedAction && observationEnabled) { | ||
@@ -61,0 +67,0 @@ return Math.max(fallback, resolveObservedActionTimeoutSec(data)); |
+52
-11
@@ -5,3 +5,4 @@ import { readFileSync, writeFileSync } from 'node:fs'; | ||
| import { runUnifiedCheck, parseCheckTimeout } from './check.js'; | ||
| import { COMMAND_MAP } from './commands.js'; | ||
| import { commandInput, findCommandSuggestions, formatCommandSuggestions, } from './commandSuggestions.js'; | ||
| import { COMMANDS, COMMAND_MAP } from './commands.js'; | ||
| import { CLIENT_VERSION } from './socket.js'; | ||
@@ -13,3 +14,3 @@ import { UnifiedClient } from './unified.js'; | ||
| title: 'API 调试', | ||
| names: ['capture_checkpoint', 'get_requests', 'get_logs', 'get_events', 'get_api_schema'], | ||
| names: ['tab_checkpoint', 'get_requests', 'get_logs', 'get_events', 'get_api_schema'], | ||
| }, | ||
@@ -50,2 +51,11 @@ { title: 'Mock & 规则', names: ['set_mock', 'get_mocks', 'set_rule', 'get_rules'] }, | ||
| ]; | ||
| const BUILTIN_COMMANDS = [ | ||
| { | ||
| name: 'check', | ||
| description: '检查指定浏览器,未指定时检查全部已连接浏览器', | ||
| examples: ['check'], | ||
| }, | ||
| ]; | ||
| const SUGGESTABLE_COMMANDS = [...COMMANDS, ...BUILTIN_COMMANDS]; | ||
| const ADVANCED_BUILTIN_COMMANDS = new Set(['connector_list']); | ||
| function optionValue(args, name) { | ||
@@ -82,3 +92,2 @@ const index = args.indexOf(name); | ||
| ' check 检查指定浏览器,未指定时检查全部已连接浏览器', | ||
| ' connector_list 列出 cloud-server 下的 connector', | ||
| ]; | ||
@@ -96,2 +105,13 @@ for (const category of CATEGORIES) { | ||
| } | ||
| function printAdvancedCommandHelp(commandName, action) { | ||
| if (action !== 'connector_list') | ||
| return; | ||
| console.error([ | ||
| 'connector_list', | ||
| ' 列出 cloud-server 下的 connector(高级诊断)', | ||
| '', | ||
| '用法:', | ||
| ` ${commandName} connector_list [--server <url>] [--timeout <seconds>]`, | ||
| ].join('\n')); | ||
| } | ||
| function printCommandHelp(commandName, command) { | ||
@@ -110,3 +130,3 @@ const lines = [ | ||
| const required = parameter.required ? '必填' : '可选'; | ||
| lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)}${parameter.description}`); | ||
| lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)} ${parameter.description}`); | ||
| } | ||
@@ -122,2 +142,14 @@ lines.push(''); | ||
| } | ||
| function printUnknownCommand(commandName, args) { | ||
| const input = commandInput(args) || args[0] || ''; | ||
| const suggestions = findCommandSuggestions(args, SUGGESTABLE_COMMANDS); | ||
| const lines = [`不支持的命令: ${input}`]; | ||
| if (suggestions.length > 0) { | ||
| lines.push('', '你可能想用:', ...formatCommandSuggestions(commandName, suggestions)); | ||
| } | ||
| else { | ||
| lines.push(`运行 ${commandName} --help 查看所有可用命令`); | ||
| } | ||
| console.error(lines.join('\n')); | ||
| } | ||
| function parsePositiveTimeout(value, fallback) { | ||
@@ -240,2 +272,4 @@ if (value === undefined) | ||
| const clientVersion = options.clientVersion ?? CLIENT_VERSION; | ||
| if (args[0] === 'capture_checkpoint') | ||
| args = ['tab_checkpoint', ...args.slice(1)]; | ||
| if (args.includes('--version') || args.includes('-v')) { | ||
@@ -253,6 +287,18 @@ console.log(clientVersion); | ||
| printCommandHelp(commandName, command); | ||
| else | ||
| else if (ADVANCED_BUILTIN_COMMANDS.has(args[0])) { | ||
| printAdvancedCommandHelp(commandName, args[0]); | ||
| } | ||
| else if (args[0]?.startsWith('-') || BUILTIN_COMMANDS.some(item => item.name === args[0])) { | ||
| printUsage(commandName, clientVersion); | ||
| } | ||
| else { | ||
| printUnknownCommand(commandName, args); | ||
| return 2; | ||
| } | ||
| return 0; | ||
| } | ||
| if (args[0] !== 'check' && args[0] !== 'connector_list' && !COMMAND_MAP.has(args[0])) { | ||
| printUnknownCommand(commandName, args); | ||
| return 2; | ||
| } | ||
| let parsed; | ||
@@ -284,7 +330,2 @@ try { | ||
| } | ||
| if (parsed.action !== 'connector_list' && !COMMAND_MAP.has(parsed.action)) { | ||
| console.error(`不支持的 action: ${parsed.action}`); | ||
| console.error(`运行 ${commandName} --help 查看所有可用命令`); | ||
| return 2; | ||
| } | ||
| if (parsed.action === 'page_selected_element' && parsed.outputPath) { | ||
@@ -311,3 +352,3 @@ parsed.payload = { ...parsed.payload, includeScreenshot: true }; | ||
| } | ||
| return 0; | ||
| return result?.action?.success === false ? 1 : 0; | ||
| } | ||
@@ -314,0 +355,0 @@ catch (error) { |
+30
-49
@@ -11,2 +11,4 @@ import type { BrowserCommandAction } from './generated-browser-action-protocol.js'; | ||
| description: string; | ||
| /** CLI 中用于推荐 canonical action 的常见表达,不会作为可执行 action。 */ | ||
| aliases?: readonly string[]; | ||
| /** payload 字段说明 */ | ||
@@ -19,4 +21,4 @@ params?: ParamDef[]; | ||
| export declare const COMMANDS: readonly [{ | ||
| readonly name: "capture_checkpoint"; | ||
| readonly description: "为目标标签页建立 console/network 增量边界"; | ||
| readonly name: "tab_checkpoint"; | ||
| readonly description: "为目标标签页建立日志、请求和埋点增量边界"; | ||
| readonly params: [{ | ||
@@ -27,3 +29,3 @@ readonly name: "tabId"; | ||
| }]; | ||
| readonly examples: ["capture_checkpoint --payload '{\"tabId\":12345}'"]; | ||
| readonly examples: ["tab_checkpoint --payload '{\"tabId\":12345}'"]; | ||
| }, { | ||
@@ -55,3 +57,3 @@ readonly name: "get_requests"; | ||
| readonly type: "string"; | ||
| readonly description: "只返回指定 capture cursor 之后的请求"; | ||
| readonly description: "只返回指定 tab checkpoint 或同类查询 cursor 之后的请求"; | ||
| }, { | ||
@@ -89,3 +91,3 @@ readonly name: "includeBody"; | ||
| readonly type: "string"; | ||
| readonly description: "只返回指定 capture cursor 之后的日志"; | ||
| readonly description: "只返回指定 tab checkpoint 或同类查询 cursor 之后的日志"; | ||
| }]; | ||
@@ -117,2 +119,6 @@ readonly examples: ["get_logs --payload '{\"limit\":10,\"level\":\"error\"}'"]; | ||
| }, { | ||
| readonly name: "after"; | ||
| readonly type: "string"; | ||
| readonly description: "只返回指定 tab checkpoint 之后的事件"; | ||
| }, { | ||
| readonly name: "limit"; | ||
@@ -392,2 +398,3 @@ readonly type: "number"; | ||
| readonly description: "打开或按 URL 规则复用 Tab,并等待新页面加载完成"; | ||
| readonly aliases: readonly ["tab_new", "new_tab", "tab_create", "create_tab"]; | ||
| readonly params: [{ | ||
@@ -435,2 +442,3 @@ readonly name: "url"; | ||
| readonly description: "关闭指定标签页"; | ||
| readonly aliases: readonly ["tab_remove", "remove_tab", "tab_delete", "delete_tab"]; | ||
| readonly params: [{ | ||
@@ -446,6 +454,7 @@ readonly name: "tabId"; | ||
| readonly description: "获取当前窗口所有标签页列表"; | ||
| readonly aliases: readonly ["tabs", "list_tabs", "show_tabs"]; | ||
| readonly examples: ["tab_list"]; | ||
| }, { | ||
| readonly name: "page_click"; | ||
| readonly description: "点击页面元素,支持 selector / text / point 三种定位方式;auto 在可见页按设备模拟状态使用可信 mouse/touch,隐藏页使用 DOM fallback;点击直接打开新标签页时 observation.openedTabs 返回目标信息"; | ||
| readonly description: "点击页面元素,支持 CSS/@ref、可见文本或视口坐标定位"; | ||
| readonly params: [{ | ||
@@ -478,8 +487,4 @@ readonly name: "selector"; | ||
| readonly type: "\"auto\" | \"dom\" | \"mouse\" | \"touch\""; | ||
| readonly description: "点击模式;auto 在可见页按设备模拟状态选择可信 mouse/touch、隐藏页走 DOM,checkbox/radio 校验状态;其他值强制指定派发方式"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项(quietMs/firstChangeTimeoutMs/timeoutMs/navigationGraceMs/navigationTimeoutMs/networkIdleMs/contentReadyAfterMs);传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| readonly description: "默认 auto:可见页使用匹配设备的可信输入,隐藏页使用 DOM;dom/mouse/touch 可强制指定派发方式"; | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -493,3 +498,3 @@ readonly type: "number"; | ||
| readonly name: "page_drag"; | ||
| readonly description: "在页面内执行拖动;from/to 均可使用视口坐标或 CSS/@ref,auto 在可见页使用可信 mouse/touch、隐藏页使用 DOM 模拟,并默认观察拖动后的页面变化"; | ||
| readonly description: "拖动页面元素或视口坐标,适用于轮播图、滑块和拖放"; | ||
| readonly params: [{ | ||
@@ -508,3 +513,3 @@ readonly name: "from"; | ||
| readonly type: "\"auto\" | \"dom\" | \"mouse\" | \"touch\""; | ||
| readonly description: "拖动输入类型;默认 auto,可见页按设备使用可信输入、隐藏页使用 DOM 模拟;dom 可显式强制后台模拟"; | ||
| readonly description: "默认 auto:可见页使用匹配设备的可信输入,隐藏页使用 DOM;dom/mouse/touch 可强制指定派发方式"; | ||
| }, { | ||
@@ -514,7 +519,3 @@ readonly name: "durationMs"; | ||
| readonly description: "拖动持续时间,0–5000ms,默认 400ms;内部自动分段移动"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -528,3 +529,3 @@ readonly type: "number"; | ||
| readonly name: "page_type"; | ||
| readonly description: "向输入框填写文本,兼容 React 受控组件;默认观察校验提示等异步变化"; | ||
| readonly description: "向输入框填写文本,兼容 React 受控组件"; | ||
| readonly params: [{ | ||
@@ -544,7 +545,3 @@ readonly name: "selector"; | ||
| readonly description: "是否先清空原有内容,默认 true"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -558,3 +555,3 @@ readonly type: "number"; | ||
| readonly name: "page_hover"; | ||
| readonly description: "将鼠标悬停在元素上,默认观察下拉菜单、提示信息等 hover 后变化"; | ||
| readonly description: "将鼠标悬停在页面元素上"; | ||
| readonly params: [{ | ||
@@ -565,7 +562,3 @@ readonly name: "selector"; | ||
| readonly description: "原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 @eN ref(必须带 @ 前缀)"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -579,3 +572,3 @@ readonly type: "number"; | ||
| readonly name: "page_scroll"; | ||
| readonly description: "滚动页面或指定容器,默认观察懒加载等滚动后变化"; | ||
| readonly description: "滚动页面或指定容器"; | ||
| readonly params: [{ | ||
@@ -598,7 +591,3 @@ readonly name: "direction"; | ||
| readonly description: "默认 self,要求 selector 本身可滚动;nearest 会在其不可滚动时使用最近的可滚动祖先"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -635,3 +624,3 @@ readonly type: "number"; | ||
| readonly name: "page_press"; | ||
| readonly description: "在页面中按下键盘按键,默认观察提交、关闭弹窗等结果"; | ||
| readonly description: "在页面中按下键盘按键或组合键"; | ||
| readonly params: [{ | ||
@@ -646,7 +635,3 @@ readonly name: "key"; | ||
| readonly description: "修饰键数组:ctrl/alt/shift/meta/cmd"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -712,3 +697,3 @@ readonly type: "number"; | ||
| readonly name: "page_upload"; | ||
| readonly description: "向 <input type=\"file\"> 元素上传文件,默认观察上传状态变化"; | ||
| readonly description: "向 <input type=\"file\"> 元素上传一个或多个文件"; | ||
| readonly params: [{ | ||
@@ -724,7 +709,3 @@ readonly name: "selector"; | ||
| readonly description: "本地文件路径数组"; | ||
| }, { | ||
| readonly name: "observe"; | ||
| readonly type: "object | false"; | ||
| readonly description: "观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回"; | ||
| }, ParamDef, { | ||
| }, ParamDef, ParamDef, { | ||
| readonly name: "tabId"; | ||
@@ -731,0 +712,0 @@ readonly type: "number"; |
+39
-50
| const PAGE_DIAGNOSTICS_PARAM = { | ||
| name: 'diagnostics', | ||
| type: 'object | boolean', | ||
| description: '交互动作默认在执行前原子建立边界,并返回新增 error logs/全部业务 requests;传 false 关闭,或传对象自定义过滤', | ||
| description: '默认关闭;传 true 返回动作期间新增的 error logs/业务 requests,或传对象自定义通道与过滤', | ||
| }; | ||
| const PAGE_OBSERVE_PARAM = { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '默认开启 DOM/导航观察;传 false 关闭,不影响显式请求的 diagnostics', | ||
| }; | ||
| export const COMMANDS = [ | ||
| // ── API 调试 ────────────────────────────────────────────────────────────── | ||
| { | ||
| name: 'capture_checkpoint', | ||
| description: '为目标标签页建立 console/network 增量边界', | ||
| name: 'tab_checkpoint', | ||
| description: '为目标标签页建立日志、请求和埋点增量边界', | ||
| params: [{ name: 'tabId', type: 'number', description: '目标标签页 ID,从 tab_open 获取' }], | ||
| examples: [`capture_checkpoint --payload '{"tabId":12345}'`], | ||
| examples: [`tab_checkpoint --payload '{"tabId":12345}'`], | ||
| }, | ||
@@ -27,3 +32,7 @@ { | ||
| { name: 'since', type: 'number', description: '只返回最近 N 秒内的请求' }, | ||
| { name: 'after', type: 'string', description: '只返回指定 capture cursor 之后的请求' }, | ||
| { | ||
| name: 'after', | ||
| type: 'string', | ||
| description: '只返回指定 tab checkpoint 或同类查询 cursor 之后的请求', | ||
| }, | ||
| { name: 'includeBody', type: 'boolean', description: '是否包含请求/响应体,默认 true' }, | ||
@@ -45,3 +54,7 @@ ], | ||
| { name: 'since', type: 'number', description: '只返回最近 N 秒内的日志' }, | ||
| { name: 'after', type: 'string', description: '只返回指定 capture cursor 之后的日志' }, | ||
| { | ||
| name: 'after', | ||
| type: 'string', | ||
| description: '只返回指定 tab checkpoint 或同类查询 cursor 之后的日志', | ||
| }, | ||
| ], | ||
@@ -63,2 +76,3 @@ examples: [`get_logs --payload '{"limit":10,"level":"error"}'`], | ||
| { name: 'since', type: 'number', description: '只返回最近 N 秒内的事件' }, | ||
| { name: 'after', type: 'string', description: '只返回指定 tab checkpoint 之后的事件' }, | ||
| { name: 'limit', type: 'number', description: '返回条数,默认 20' }, | ||
@@ -317,2 +331,3 @@ { | ||
| description: '打开或按 URL 规则复用 Tab,并等待新页面加载完成', | ||
| aliases: ['tab_new', 'new_tab', 'tab_create', 'create_tab'], | ||
| params: [ | ||
@@ -367,2 +382,3 @@ { name: 'url', type: 'string', required: true, description: '要打开的 URL' }, | ||
| description: '关闭指定标签页', | ||
| aliases: ['tab_remove', 'remove_tab', 'tab_delete', 'delete_tab'], | ||
| params: [{ name: 'tabId', type: 'number', required: true, description: '要关闭的标签页 ID' }], | ||
@@ -374,2 +390,3 @@ examples: [`tab_close --payload '{"tabId":123}'`], | ||
| description: '获取当前窗口所有标签页列表', | ||
| aliases: ['tabs', 'list_tabs', 'show_tabs'], | ||
| examples: ['tab_list'], | ||
@@ -380,3 +397,3 @@ }, | ||
| name: 'page_click', | ||
| description: '点击页面元素,支持 selector / text / point 三种定位方式;auto 在可见页按设备模拟状态使用可信 mouse/touch,隐藏页使用 DOM fallback;点击直接打开新标签页时 observation.openedTabs 返回目标信息', | ||
| description: '点击页面元素,支持 CSS/@ref、可见文本或视口坐标定位', | ||
| params: [ | ||
@@ -416,9 +433,5 @@ { | ||
| type: '"auto" | "dom" | "mouse" | "touch"', | ||
| description: '点击模式;auto 在可见页按设备模拟状态选择可信 mouse/touch、隐藏页走 DOM,checkbox/radio 校验状态;其他值强制指定派发方式', | ||
| description: '默认 auto:可见页使用匹配设备的可信输入,隐藏页使用 DOM;dom/mouse/touch 可强制指定派发方式', | ||
| }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项(quietMs/firstChangeTimeoutMs/timeoutMs/navigationGraceMs/navigationTimeoutMs/networkIdleMs/contentReadyAfterMs);传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -439,3 +452,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_drag', | ||
| description: '在页面内执行拖动;from/to 均可使用视口坐标或 CSS/@ref,auto 在可见页使用可信 mouse/touch、隐藏页使用 DOM 模拟,并默认观察拖动后的页面变化', | ||
| description: '拖动页面元素或视口坐标,适用于轮播图、滑块和拖放', | ||
| params: [ | ||
@@ -457,3 +470,3 @@ { | ||
| type: '"auto" | "dom" | "mouse" | "touch"', | ||
| description: '拖动输入类型;默认 auto,可见页按设备使用可信输入、隐藏页使用 DOM 模拟;dom 可显式强制后台模拟', | ||
| description: '默认 auto:可见页使用匹配设备的可信输入,隐藏页使用 DOM;dom/mouse/touch 可强制指定派发方式', | ||
| }, | ||
@@ -465,7 +478,3 @@ { | ||
| }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -481,3 +490,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_type', | ||
| description: '向输入框填写文本,兼容 React 受控组件;默认观察校验提示等异步变化', | ||
| description: '向输入框填写文本,兼容 React 受控组件', | ||
| params: [ | ||
@@ -492,7 +501,3 @@ { | ||
| { name: 'clearFirst', type: 'boolean', description: '是否先清空原有内容,默认 true' }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -505,3 +510,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_hover', | ||
| description: '将鼠标悬停在元素上,默认观察下拉菜单、提示信息等 hover 后变化', | ||
| description: '将鼠标悬停在页面元素上', | ||
| params: [ | ||
@@ -514,7 +519,3 @@ { | ||
| }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -527,3 +528,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_scroll', | ||
| description: '滚动页面或指定容器,默认观察懒加载等滚动后变化', | ||
| description: '滚动页面或指定容器', | ||
| params: [ | ||
@@ -547,7 +548,3 @@ { | ||
| }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -585,3 +582,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_press', | ||
| description: '在页面中按下键盘按键,默认观察提交、关闭弹窗等结果', | ||
| description: '在页面中按下键盘按键或组合键', | ||
| params: [ | ||
@@ -595,7 +592,3 @@ { | ||
| { name: 'modifiers', type: 'string[]', description: '修饰键数组:ctrl/alt/shift/meta/cmd' }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -660,3 +653,3 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, | ||
| name: 'page_upload', | ||
| description: '向 <input type="file"> 元素上传文件,默认观察上传状态变化', | ||
| description: '向 <input type="file"> 元素上传一个或多个文件', | ||
| params: [ | ||
@@ -670,7 +663,3 @@ { | ||
| { name: 'filePaths', type: 'string[]', required: true, description: '本地文件路径数组' }, | ||
| { | ||
| name: 'observe', | ||
| type: 'object | false', | ||
| description: '观察选项;传 false 只关闭 DOM/导航观察,默认诊断仍会返回', | ||
| }, | ||
| PAGE_OBSERVE_PARAM, | ||
| PAGE_DIAGNOSTICS_PARAM, | ||
@@ -677,0 +666,0 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' }, |
@@ -7,3 +7,3 @@ export interface GetRequestsParams { | ||
| since?: number; | ||
| /** Opaque cursor returned by capture_checkpoint or a previous query. */ | ||
| /** Opaque cursor returned by tab_checkpoint or a previous query. */ | ||
| after?: string; | ||
@@ -23,3 +23,3 @@ includeBody?: boolean; | ||
| since?: number; | ||
| /** Opaque cursor returned by capture_checkpoint or a previous query. */ | ||
| /** Opaque cursor returned by tab_checkpoint or a previous query. */ | ||
| after?: string; | ||
@@ -44,2 +44,4 @@ limit?: number; | ||
| since?: number; | ||
| /** Opaque cursor returned by tab_checkpoint or a previous event query. */ | ||
| after?: string; | ||
| limit?: number; | ||
@@ -305,2 +307,3 @@ includeRaw?: boolean; | ||
| requests?: boolean | Pick<GetRequestsParams, 'count' | 'filter' | 'includeBody' | 'includeHeaders' | 'maxBodySize' | 'fields' | 'keysOnly' | 'source'>; | ||
| events?: boolean | Pick<GetEventsParams, 'source' | 'event_type' | 'filter' | 'limit' | 'includeRaw'>; | ||
| } | ||
@@ -478,2 +481,7 @@ export type PageDiagnosticsSetting = PageDiagnosticsOptions | boolean; | ||
| export interface BrowserActionMap { | ||
| tab_checkpoint: { | ||
| request: CaptureCheckpointParams; | ||
| response: CaptureCheckpointResult; | ||
| }; | ||
| /** Compatibility alias for tab_checkpoint. */ | ||
| capture_checkpoint: { | ||
@@ -660,2 +668,3 @@ request: CaptureCheckpointParams; | ||
| export declare const BROWSER_ACTIONS: { | ||
| readonly tab_checkpoint: true; | ||
| readonly capture_checkpoint: true; | ||
@@ -706,2 +715,3 @@ readonly get_requests: true; | ||
| export declare const BROWSER_COMMAND_ACTIONS: { | ||
| readonly tab_checkpoint: true; | ||
| readonly capture_checkpoint: true; | ||
@@ -751,2 +761,3 @@ readonly get_requests: true; | ||
| export declare const EXTENSION_BROWSER_ACTIONS: { | ||
| readonly tab_checkpoint: true; | ||
| readonly capture_checkpoint: true; | ||
@@ -753,0 +764,0 @@ readonly get_requests: true; |
@@ -20,2 +20,3 @@ // Generated by scripts/generate-client-action-protocol.mjs. | ||
| export const BROWSER_ACTIONS = { | ||
| tab_checkpoint: true, | ||
| capture_checkpoint: true, | ||
@@ -66,2 +67,3 @@ get_requests: true, | ||
| export const BROWSER_COMMAND_ACTIONS = { | ||
| tab_checkpoint: true, | ||
| capture_checkpoint: true, | ||
@@ -111,2 +113,3 @@ get_requests: true, | ||
| export const EXTENSION_BROWSER_ACTIONS = { | ||
| tab_checkpoint: true, | ||
| capture_checkpoint: true, | ||
@@ -113,0 +116,0 @@ get_requests: true, |
+3
-1
@@ -13,3 +13,5 @@ /** | ||
| import type * as Protocol from './generated-browser-action-protocol.js'; | ||
| export declare function captureCheckpoint(params?: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'capture_checkpoint'>>; | ||
| export declare function tabCheckpoint(params?: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>; | ||
| /** Compatibility wrapper for tabCheckpoint. */ | ||
| export declare function captureCheckpoint(params?: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>; | ||
| export declare function getRequests(params?: Protocol.GetRequestsParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_requests'>>; | ||
@@ -16,0 +18,0 @@ export declare function getLogs(params?: Protocol.GetLogsParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_logs'>>; |
+5
-1
@@ -10,4 +10,8 @@ /** | ||
| import { invoke } from './unified.js'; | ||
| export function tabCheckpoint(params = {}, options) { | ||
| return invoke('tab_checkpoint', params, options); | ||
| } | ||
| /** Compatibility wrapper for tabCheckpoint. */ | ||
| export function captureCheckpoint(params = {}, options) { | ||
| return invoke('capture_checkpoint', params, options); | ||
| return tabCheckpoint(params, options); | ||
| } | ||
@@ -14,0 +18,0 @@ export function getRequests(params = {}, options) { |
+11
-3
@@ -208,3 +208,8 @@ import WebSocket from 'ws'; | ||
| } | ||
| throw new Error('The browser host for browser_launch is ambiguous. Pass --local, --connector <id|name>, or --browser <global-id>.'); | ||
| const availableHosts = [ | ||
| ...(localAvailable ? ['this machine [--local]'] : []), | ||
| ...connectors.map(connector => `${connector.name} [${connector.connectorId}]`), | ||
| ].join(', '); | ||
| throw new Error('The browser host for browser_launch is ambiguous. ' + | ||
| `Pass --local, --connector <id|name>, or --browser <global-id>. Available: ${availableHosts}`); | ||
| } | ||
@@ -265,7 +270,10 @@ async selectDefaultBrowser(data, options, timeoutSec) { | ||
| const selection = resolveCloudConnectorSelector(connectors, selector); | ||
| const available = connectors | ||
| .map(connector => `${connector.name} [${connector.connectorId}]`) | ||
| .join(', '); | ||
| if (selection.status === 'ambiguous') { | ||
| throw new Error(`Cloud connector selector "${selector}" is ambiguous. Use an exact connectorId.`); | ||
| throw new Error(`Cloud connector selector "${selector}" is ambiguous. Use an exact connectorId. Available: ${available}`); | ||
| } | ||
| if (selection.status !== 'matched') { | ||
| throw new Error(`No cloud connector matches "${selector}".`); | ||
| throw new Error(`No cloud connector matches "${selector}". Available: ${available}`); | ||
| } | ||
@@ -272,0 +280,0 @@ return selection.connector; |
+3
-2
| { | ||
| "name": "@mearl/client", | ||
| "version": "2.7.1", | ||
| "version": "2.7.2", | ||
| "description": "Unified Mearl SDK & CLI for local and remote browsers", | ||
@@ -51,4 +51,5 @@ "type": "module", | ||
| "dependencies": { | ||
| "fastest-levenshtein": "^1.0.16", | ||
| "ws": "^8.18.0", | ||
| "@mearl/cloud-types": "2.7.1" | ||
| "@mearl/cloud-types": "2.7.2" | ||
| }, | ||
@@ -55,0 +56,0 @@ "devDependencies": { |
+6
-3
@@ -76,2 +76,5 @@ # @mearl/client | ||
| 输入不存在或拼写有误的 action 时,CLI 会基于可用命令和常见别名直接返回最多三个候选及调用示例, | ||
| 不连接浏览器,也不会自动执行推荐命令。例如 `mearl tab new` 会优先推荐 `mearl tab_open`。 | ||
| **通用选项:** | ||
@@ -95,3 +98,3 @@ | ||
| | ----------- | --------------------------------------------------------------------------------------------------------------------- | | ||
| | API 调试 | `capture_checkpoint` `get_requests` `get_logs` `get_events` `get_api_schema` | | ||
| | API 调试 | `tab_checkpoint` `get_requests` `get_logs` `get_events` `get_api_schema` | | ||
| | Mock & 规则 | `set_mock` `get_mocks` `set_rule` `get_rules` | | ||
@@ -108,7 +111,7 @@ | 网络代理 | `send_request` `send_mtop_request` | | ||
| 页面交互动作(`page_click` / `page_drag` / `page_type` / `page_hover` / `page_scroll` / `page_press` / `page_upload`)默认内置观察与原子诊断:同一次调用内执行动作、等待异步稳定,并返回 `{ action, observation, diagnostics }`;`diagnostics` 默认包含新增 error logs 和全部业务 requests。传 `observe: false` 只关闭 DOM/导航观察并保留诊断;同时传 `diagnostics: false` 才仅执行裸动作。观察结果不替代完整页面理解:`mode: "delta"` 只返回主文档中的 `effects.notifications`、`effects.interactives` 和 `effects.focus` 等高置信度信号,并明确携带 `scope: "main-document"`;可交互节点会尽量携带真实 backend `node.ref`,后续动作优先使用 ref,缺少 ref 时使用 `node.selector`。`mode: "navigation"` 且 `ready: true` 时,页面已通过网络静默、骨架状态或保守的内容稳定判定,可在新页面重建快照。动作直接打开新标签页时,`observation.openedTabs` 返回新标签页的 `tabId`、URL、标题和加载状态,可直接把该 `tabId` 用于后续操作,不必调用 `tab_list`。通常仅在 `fullSnapshotRecommended` 为 true 时根据 `snapshotReasons` 回退;滚动后若下一步需要读取新视口内容,可按需获取 viewport 快照。`page_eval` 默认裸执行;显式传 `observe` 对象可启用观察,传 `diagnostics: true` 或对象可启用原子诊断。 | ||
| 页面交互动作(`page_click` / `page_drag` / `page_type` / `page_hover` / `page_scroll` / `page_press` / `page_upload`)默认等待异步稳定并返回 `{ action, observation }`。参数、ref 或目标解析在派发前失败时返回 `action.stage: "precondition"`,省略无意义的 `observation` / `diagnostics`,CLI 退出码为 1。传 `observe: false` 可执行裸动作;需要同一动作期间的新增 error logs 和业务 requests 时传 `diagnostics: true`,埋点通过 `diagnostics.events` 按需开启。`page_eval` 默认裸执行,可按需开启观察或诊断。 | ||
| `page_snapshot` 默认返回完整 AX Tree;长列表可传 `mode: "viewport"`,只需要当前视口内的控件时传 `mode: "interactive"`,已知 CSS 区域时传 `rootSelector`,已有 ref 时传 `rootRef`(可用 `ancestorDepth` 向上补充上下文),只查找特定文案或角色时传 `query`。视口内缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。`maxNodes` / `maxChars` 截断会同时保留首尾内容。 | ||
| 重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。`page_click.clickMode` 默认 `auto`:可见桌面页派发可信 mouse,移动模拟页派发可信 touch;隐藏页使用 DOM fallback,返回 `dispatchMode: "dom"` 和 `fallbackReason: "page-hidden"`,且不切换标签或还原窗口。可用 `dom` / `mouse` / `touch` 覆盖自动策略;可信输入返回实际 `pointerType`。点击结果的 `resolvedTarget` 和滚动结果的前后位置、边界字段可用于诊断实际派发目标与滚动效果。 | ||
| 重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。点击结果的 `resolvedTarget` 和滚动结果的前后位置、边界字段可用于诊断实际目标与效果。 | ||
@@ -115,0 +118,0 @@ `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。 |
232756
1.12%30
7.14%5132
2.33%143
2.14%3
50%+ Added
+ Added
+ Added
- Removed
Updated