@mtop-devtools/client
Advanced tools
@@ -27,3 +27,3 @@ /** | ||
| * left to the native-hosts (they overwrite their own record on reconnect and | ||
| * reconcile on browser_list_browsers). Otherwise many concurrent clients pruning at once | ||
| * reconcile when serving browser_list). Otherwise many concurrent clients pruning at once | ||
| * could, in a narrow window, delete a record a browser just re-published on | ||
@@ -30,0 +30,0 @@ * reconnect — making a live browser briefly vanish from the registry. |
@@ -32,3 +32,3 @@ /** | ||
| * left to the native-hosts (they overwrite their own record on reconnect and | ||
| * reconcile on browser_list_browsers). Otherwise many concurrent clients pruning at once | ||
| * reconcile when serving browser_list). Otherwise many concurrent clients pruning at once | ||
| * could, in a narrow window, delete a record a browser just re-published on | ||
@@ -35,0 +35,0 @@ * reconnect — making a live browser briefly vanish from the registry. |
+2
-35
@@ -16,3 +16,2 @@ #!/usr/bin/env node | ||
| import { invoke, CLIENT_VERSION } from './socket.js'; | ||
| import { listLiveBrowsers, findBrowser, describeBrowser } from './browserRegistry.js'; | ||
| import { COMMAND_MAP } from './commands.js'; | ||
@@ -51,4 +50,4 @@ import { runCheck } from './check.js'; | ||
| { | ||
| title: '托管浏览器', | ||
| names: ['browser_launch', 'browser_list_managed', 'browser_close'], | ||
| title: '浏览器', | ||
| names: ['browser_list', 'browser_launch', 'browser_close'], | ||
| }, | ||
@@ -79,3 +78,2 @@ ]; | ||
| ' check 检测环境配置,排查连接问题', | ||
| ' browsers 列出当前已连接的浏览器(多浏览器寻址)', | ||
| ]; | ||
@@ -248,29 +246,2 @@ for (const cat of CATEGORIES) { | ||
| } | ||
| function runBrowsers() { | ||
| const browsers = listLiveBrowsers(); | ||
| if (browsers.length === 0) { | ||
| console.error('当前没有已连接的浏览器。'); | ||
| console.error('确认浏览器已安装 Mtop DevTools 插件并打开过面板,或已运行 CDP daemon。'); | ||
| console.error('若插件已安装且面板已打开,多半是 native-host 过旧(浏览器列表由 native-host 发布):'); | ||
| console.error(' npm install -g @mtop-devtools/native-host@latest 然后重新打开面板'); | ||
| return; | ||
| } | ||
| // Default target mirrors socket.ts resolution: explicit env selector wins, | ||
| // otherwise the most-recently-focused browser (index 0). | ||
| const envSelector = process.env.MTOP_DEVTOOLS_BROWSER; | ||
| const defaultTarget = envSelector | ||
| ? (findBrowser(browsers, envSelector) ?? browsers[0]) | ||
| : browsers[0]; | ||
| console.error(`已连接 ${browsers.length} 个浏览器(默认目标标 ★):\n`); | ||
| for (const b of browsers) { | ||
| const marker = b.browserId === defaultTarget.browserId ? '★' : ' '; | ||
| const via = b.transport === 'cdp' ? ' [CDP]' : ''; | ||
| const ext = b.extensionVersion ? ` ext v${b.extensionVersion}` : ''; | ||
| console.error(` ${marker} ${describeBrowser(b)}${via}${ext}`); | ||
| console.error(` id: ${b.browserId}`); | ||
| console.error(` socket: ${b.socketPath}`); | ||
| } | ||
| console.error('\n用 `mtop-devtools <action> --browser <id|名称>` 指定目标;'); | ||
| console.error('或设置环境变量 MTOP_DEVTOOLS_BROWSER 固定默认目标。'); | ||
| } | ||
| async function main() { | ||
@@ -282,6 +253,2 @@ // Handle built-in subcommands before normal arg parsing | ||
| } | ||
| if (process.argv[2] === 'browsers') { | ||
| runBrowsers(); | ||
| return; | ||
| } | ||
| let args; | ||
@@ -288,0 +255,0 @@ try { |
@@ -17,3 +17,3 @@ export interface ParamDef { | ||
| export declare const COMMANDS: CommandDef[]; | ||
| export type KnownAction = 'get_requests' | 'get_logs' | 'get_events' | 'get_api_schema' | 'set_mock' | 'get_mocks' | 'add_rule' | 'proxy_request' | 'send_mtop_request' | 'tab_open' | 'tab_close' | 'tab_list' | 'page_click' | 'page_type' | 'page_hover' | 'page_scroll' | 'page_eval' | 'page_press' | 'page_wait' | 'page_navigate' | 'page_upload' | 'page_snapshot' | 'get_screenshot' | 'get_selected_element' | 'page_frames' | 'set_device_emulation' | 'set_timezone' | 'record_start' | 'record_stop' | 'record_get' | 'request_domain_permission' | 'get_user_info' | 'page_act' | 'run_actions'; | ||
| export type KnownAction = 'get_requests' | 'get_logs' | 'get_events' | 'get_api_schema' | 'set_mock' | 'get_mocks' | 'add_rule' | 'proxy_request' | 'send_mtop_request' | 'tab_open' | 'tab_close' | 'tab_list' | 'page_click' | 'page_type' | 'page_hover' | 'page_scroll' | 'page_eval' | 'page_press' | 'page_wait' | 'page_navigate' | 'page_upload' | 'page_snapshot' | 'get_screenshot' | 'get_selected_element' | 'page_frames' | 'set_device_emulation' | 'set_timezone' | 'record_start' | 'record_stop' | 'record_get' | 'request_domain_permission' | 'get_user_info' | 'browser_list' | 'browser_launch' | 'browser_close' | 'page_act' | 'run_actions'; | ||
| export declare const COMMAND_MAP: Map<string, CommandDef>; |
+6
-6
@@ -149,4 +149,9 @@ export const COMMANDS = [ | ||
| }, | ||
| // ── 托管浏览器 ──────────────────────────────────────────────────────────── | ||
| // ── 浏览器 ──────────────────────────────────────────────────────────────── | ||
| { | ||
| name: 'browser_list', | ||
| description: '列出所有浏览器及其连接状态、类型和托管实例信息', | ||
| examples: ['browser_list'], | ||
| }, | ||
| { | ||
| name: 'browser_launch', | ||
@@ -190,7 +195,2 @@ description: '启动独立 Chrome,可在新实例中通过 TDBank 登录指定账号', | ||
| { | ||
| name: 'browser_list_managed', | ||
| description: '列出由 Mtop DevTools 启动的托管浏览器', | ||
| examples: ['browser_list_managed'], | ||
| }, | ||
| { | ||
| name: 'browser_close', | ||
@@ -197,0 +197,0 @@ description: '关闭指定托管浏览器并按策略清理独立 Profile', |
+29
-6
@@ -105,16 +105,40 @@ /** | ||
| export declare function tdbankAccount(params?: TdbankAccountParams): Promise<any>; | ||
| export interface BrowserInfo { | ||
| export type BrowserStatus = 'connected' | 'running_disconnected' | 'stopped'; | ||
| export interface BrowserInfoBase { | ||
| browserId: string; | ||
| name: string; | ||
| status: BrowserStatus; | ||
| extensionVersion?: string; | ||
| nativeHostVersion?: string; | ||
| transport?: 'extension' | 'cdp'; | ||
| isDefaultTarget: boolean; | ||
| } | ||
| export interface ListBrowsersResult { | ||
| export interface RegularBrowserInfo extends BrowserInfoBase { | ||
| type: 'regular'; | ||
| status: 'connected'; | ||
| } | ||
| export interface ManagedBrowserAccount { | ||
| accountId?: string; | ||
| loginId?: string; | ||
| nickname: string; | ||
| } | ||
| export interface ManagedBrowserInfo extends BrowserInfoBase { | ||
| type: 'managed'; | ||
| headless: boolean; | ||
| persistent: boolean; | ||
| cdpPort: number; | ||
| createdAt: string; | ||
| account?: ManagedBrowserAccount; | ||
| copiedCookies?: { | ||
| domains: string[]; | ||
| count: number; | ||
| }; | ||
| } | ||
| export type BrowserInfo = RegularBrowserInfo | ManagedBrowserInfo; | ||
| export interface BrowserListResult { | ||
| count: number; | ||
| default: string; | ||
| connectedCount: number; | ||
| defaultBrowserId: string | null; | ||
| browsers: BrowserInfo[]; | ||
| } | ||
| export declare function listBrowsers(): Promise<ListBrowsersResult>; | ||
| export declare function browserList(): Promise<BrowserListResult>; | ||
| export interface SelectBrowserParams { | ||
@@ -145,3 +169,2 @@ browser?: string; | ||
| export declare function browserLaunch(params: BrowserLaunchParams): Promise<any>; | ||
| export declare function browserListManaged(): Promise<any>; | ||
| export interface BrowserCloseParams { | ||
@@ -148,0 +171,0 @@ browser: string; |
+2
-5
@@ -47,4 +47,4 @@ /** | ||
| } | ||
| export function listBrowsers() { | ||
| return invoke('browser_list_browsers', {}); | ||
| export function browserList() { | ||
| return invoke('browser_list', {}); | ||
| } | ||
@@ -57,5 +57,2 @@ export function selectBrowser(params = {}) { | ||
| } | ||
| export function browserListManaged() { | ||
| return invoke('browser_list_managed', {}); | ||
| } | ||
| export function browserClose(params) { | ||
@@ -62,0 +59,0 @@ return invoke('browser_close', params); |
+1
-0
@@ -27,2 +27,3 @@ /** | ||
| export declare function getDefaultBrowser(): string | undefined; | ||
| export declare function decorateBrowserListResult(result: Record<string, any>): Record<string, any>; | ||
| /** | ||
@@ -29,0 +30,0 @@ * 向 native host 发送一个 action 请求并等待响应。 |
+31
-23
@@ -39,16 +39,20 @@ /** | ||
| } | ||
| function listBrowsersResult() { | ||
| const browsers = listLiveBrowsers(); | ||
| const current = getDefaultBrowser(); | ||
| function findConnectedBrowser(items, selector) { | ||
| const normalized = selector.trim().toLowerCase(); | ||
| const connected = items.filter(item => item.status === 'connected'); | ||
| return (connected.find(item => String(item.browserId).toLowerCase() === normalized) ?? | ||
| connected.find(item => String(item.name).toLowerCase() === normalized) ?? | ||
| connected.find(item => String(item.browserId).toLowerCase().includes(normalized) || | ||
| String(item.name).toLowerCase().includes(normalized)) ?? | ||
| null); | ||
| } | ||
| export function decorateBrowserListResult(result) { | ||
| const browsers = Array.isArray(result?.browsers) ? result.browsers : []; | ||
| const selector = getDefaultBrowser() ?? process.env.MTOP_DEVTOOLS_BROWSER; | ||
| const selected = selector | ||
| ? findConnectedBrowser(browsers, selector) | ||
| : (browsers.find(browser => browser.status === 'connected') ?? null); | ||
| return { | ||
| count: browsers.length, | ||
| default: current ?? '(auto: most-recently focused browser)', | ||
| browsers: browsers.map((browser, index) => ({ | ||
| browserId: browser.browserId, | ||
| name: browser.browserName, | ||
| extensionVersion: browser.extensionVersion, | ||
| nativeHostVersion: browser.nativeHostVersion, | ||
| transport: browser.transport, | ||
| isDefaultTarget: current ? browser.browserId === current : index === 0, | ||
| })), | ||
| ...result, | ||
| defaultBrowserId: selected?.browserId ?? null, | ||
| }; | ||
@@ -72,16 +76,19 @@ } | ||
| } | ||
| /** Whether we have already attempted to auto-spawn native-host in this process. */ | ||
| /** Whether this process has already attempted to auto-spawn a persistent native-host. */ | ||
| let spawnAttempted = false; | ||
| /** | ||
| * Attempt to spawn `mtop-devtools-native-host` in the background (detached). | ||
| * Inventory-only hosts serve one browser_list request without publishing a fake CDP target. | ||
| * Returns true if a spawn was attempted (regardless of success). | ||
| */ | ||
| async function trySpawnNativeHost() { | ||
| if (spawnAttempted) | ||
| async function trySpawnNativeHost(registerBrowser = true) { | ||
| if (registerBrowser && spawnAttempted) | ||
| return false; | ||
| spawnAttempted = true; | ||
| if (registerBrowser) | ||
| spawnAttempted = true; | ||
| try { | ||
| const { spawn } = await import('node:child_process'); | ||
| return await new Promise(resolve => { | ||
| const child = spawn('mtop-devtools-native-host', ['--daemon'], { | ||
| const args = registerBrowser ? ['--daemon'] : ['--daemon', '--skip-browser-registration']; | ||
| const child = spawn('mtop-devtools-native-host', args, { | ||
| detached: true, | ||
@@ -118,4 +125,2 @@ stdio: 'ignore', | ||
| export function invoke(action, data = {}, options = {}) { | ||
| if (action === 'browser_list_browsers') | ||
| return Promise.resolve(listBrowsersResult()); | ||
| if (action === 'browser_select_browser') { | ||
@@ -132,2 +137,5 @@ try { | ||
| const resolvedData = resolveFileRefs(data); | ||
| if (action === 'browser_list') { | ||
| return dispatchInvoke(action, resolvedData, timeoutSec, undefined, undefined).then(decorateBrowserListResult); | ||
| } | ||
| const selector = browser ?? defaultBrowser ?? process.env.MTOP_DEVTOOLS_BROWSER ?? undefined; | ||
@@ -181,5 +189,5 @@ return dispatchInvoke(action, resolvedData, timeoutSec, transport, selector); | ||
| if (err.code === 'ENOENT' && !isRetry) { | ||
| const spawned = await trySpawnNativeHost(); | ||
| const spawned = await trySpawnNativeHost(action !== 'browser_list'); | ||
| if (spawned) { | ||
| // Wait for the native host to start listening and register itself. | ||
| // Wait for the native host to start listening; persistent hosts also publish their registry record. | ||
| await new Promise(r => setTimeout(r, 1200)); | ||
@@ -301,4 +309,4 @@ const browsers = listLiveBrowsers(); | ||
| '', | ||
| '用 `mtop-devtools browsers` 查看完整列表;可用 browserId 或名称匹配。', | ||
| '用 `mtop-devtools browser_list` 查看完整列表;可用 browserId 或名称匹配。', | ||
| ].join('\n'); | ||
| } |
+1
-1
| { | ||
| "name": "@mtop-devtools/client", | ||
| "version": "1.40.0", | ||
| "version": "1.40.1", | ||
| "description": "Client SDK & CLI for Mtop DevTools — communicate with Chrome Extension via Unix Socket", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+4
-2
@@ -59,3 +59,3 @@ # @mtop-devtools/client | ||
| mtop-devtools browser_launch --payload '{"name":"cookie-copy","copyCookieDomains":["example.com"]}' | ||
| mtop-devtools browsers | ||
| mtop-devtools browser_list | ||
| ``` | ||
@@ -87,3 +87,3 @@ | ||
| | TDBank | `tdbank_account` | | ||
| | 托管浏览器 | `browser_launch` `browser_list_managed` `browser_close` | | ||
| | 浏览器 | `browser_list` `browser_launch` `browser_close` | | ||
@@ -94,2 +94,4 @@ `page_act` 将一次点击、输入、滚动等动作与动作后的异步稳定判断合并为一个调用。它不替代完整页面理解:`mode: "delta"` 只返回主文档中的 `effects.notifications`、`effects.interactives` 和 `effects.focus` 等高置信度信号,并明确携带 `scope: "main-document"`;`mode: "navigation"` 且 `ready: true` 时,页面已通过网络静默、骨架状态或保守的内容稳定判定,可在新页面重建快照。仅在 `fullSnapshotRecommended` 为 true 时根据 `snapshotReasons` 回退到完整快照或截图。 | ||
| `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。 | ||
| 托管浏览器使用独立 Profile 和动态 CDP 端口。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,新实例可以使用 `headless: true`(默认)完成测试账号登录。`copyCookieDomains` 可把控制浏览器指定域的 Cookie 复制到新实例,Cookie 值不会出现在命令结果或日志中。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。 | ||
@@ -96,0 +98,0 @@ |
119
1.71%99006
-0.17%2237
-0.18%