@coofly/agent-browser-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.dockerignore +7 -0
  2. package/CLAUDE.md +82 -0
  3. package/Dockerfile +41 -0
  4. package/LICENSE +674 -0
  5. package/README.md +191 -0
  6. package/README_ZH.md +191 -0
  7. package/config.example.yaml +44 -0
  8. package/dist/config.d.ts +43 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +21 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +15 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/server.d.ts +43 -0
  17. package/dist/server.d.ts.map +1 -0
  18. package/dist/server.js +334 -0
  19. package/dist/server.js.map +1 -0
  20. package/dist/test-cli.d.ts +2 -0
  21. package/dist/test-cli.d.ts.map +1 -0
  22. package/dist/test-cli.js +22 -0
  23. package/dist/test-cli.js.map +1 -0
  24. package/dist/test.d.ts +2 -0
  25. package/dist/test.d.ts.map +1 -0
  26. package/dist/test.js +17 -0
  27. package/dist/test.js.map +1 -0
  28. package/dist/tools/advanced.d.ts +19 -0
  29. package/dist/tools/advanced.d.ts.map +1 -0
  30. package/dist/tools/advanced.js +40 -0
  31. package/dist/tools/advanced.js.map +1 -0
  32. package/dist/tools/information.d.ts +23 -0
  33. package/dist/tools/information.d.ts.map +1 -0
  34. package/dist/tools/information.js +41 -0
  35. package/dist/tools/information.js.map +1 -0
  36. package/dist/tools/interaction.d.ts +27 -0
  37. package/dist/tools/interaction.d.ts.map +1 -0
  38. package/dist/tools/interaction.js +49 -0
  39. package/dist/tools/interaction.js.map +1 -0
  40. package/dist/tools/navigation.d.ts +21 -0
  41. package/dist/tools/navigation.d.ts.map +1 -0
  42. package/dist/tools/navigation.js +37 -0
  43. package/dist/tools/navigation.js.map +1 -0
  44. package/dist/tools/storage.d.ts +16 -0
  45. package/dist/tools/storage.d.ts.map +1 -0
  46. package/dist/tools/storage.js +28 -0
  47. package/dist/tools/storage.js.map +1 -0
  48. package/dist/utils/configLoader.d.ts +19 -0
  49. package/dist/utils/configLoader.d.ts.map +1 -0
  50. package/dist/utils/configLoader.js +163 -0
  51. package/dist/utils/configLoader.js.map +1 -0
  52. package/dist/utils/executor.d.ts +26 -0
  53. package/dist/utils/executor.d.ts.map +1 -0
  54. package/dist/utils/executor.js +71 -0
  55. package/dist/utils/executor.js.map +1 -0
  56. package/package.json +35 -0
  57. package/src/config.ts +60 -0
  58. package/src/index.ts +16 -0
  59. package/src/server.ts +388 -0
  60. package/src/test-cli.ts +27 -0
  61. package/src/test.ts +20 -0
  62. package/src/tools/advanced.ts +55 -0
  63. package/src/tools/information.ts +54 -0
  64. package/src/tools/interaction.ts +74 -0
  65. package/src/tools/navigation.ts +51 -0
  66. package/src/tools/storage.ts +36 -0
  67. package/src/utils/configLoader.ts +195 -0
  68. package/src/utils/executor.ts +100 -0
  69. package/tsconfig.json +18 -0
package/src/server.ts ADDED
@@ -0,0 +1,388 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ Tool,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import { createServer as createHttpServer } from 'http';
10
+ import { getConfig } from './utils/configLoader.js';
11
+ import type { AppConfig } from './config.js';
12
+
13
+ import * as navigation from './tools/navigation.js';
14
+ import * as interaction from './tools/interaction.js';
15
+ import * as information from './tools/information.js';
16
+ import * as storage from './tools/storage.js';
17
+ import * as advanced from './tools/advanced.js';
18
+
19
+ interface SessionOptions {
20
+ session?: string;
21
+ profile?: string;
22
+ headed?: boolean;
23
+ /** CDP 远程端点地址 */
24
+ cdp?: string;
25
+ }
26
+
27
+ let globalOptions: SessionOptions = {};
28
+ let appConfig: AppConfig | null = null;
29
+
30
+ const tools: Tool[] = [
31
+ // 导航工具
32
+ {
33
+ name: 'browser_open',
34
+ description: '打开指定 URL',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: { url: { type: 'string', description: '要打开的 URL' } },
38
+ required: ['url'],
39
+ },
40
+ },
41
+ {
42
+ name: 'browser_back',
43
+ description: '浏览器后退',
44
+ inputSchema: { type: 'object', properties: {} },
45
+ },
46
+ {
47
+ name: 'browser_forward',
48
+ description: '浏览器前进',
49
+ inputSchema: { type: 'object', properties: {} },
50
+ },
51
+ {
52
+ name: 'browser_reload',
53
+ description: '刷新当前页面',
54
+ inputSchema: { type: 'object', properties: {} },
55
+ },
56
+ {
57
+ name: 'browser_get_url',
58
+ description: '获取当前页面 URL',
59
+ inputSchema: { type: 'object', properties: {} },
60
+ },
61
+ {
62
+ name: 'browser_get_title',
63
+ description: '获取当前页面标题',
64
+ inputSchema: { type: 'object', properties: {} },
65
+ },
66
+ // 交互工具
67
+ {
68
+ name: 'browser_click',
69
+ description: '点击元素',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ selector: { type: 'string', description: '元素选择器或 ref' },
74
+ button: { type: 'string', enum: ['left', 'right', 'middle'] },
75
+ },
76
+ required: ['selector'],
77
+ },
78
+ },
79
+ {
80
+ name: 'browser_type',
81
+ description: '在元素中输入文本(逐字符)',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ selector: { type: 'string', description: '元素选择器' },
86
+ text: { type: 'string', description: '要输入的文本' },
87
+ },
88
+ required: ['selector', 'text'],
89
+ },
90
+ },
91
+ {
92
+ name: 'browser_fill',
93
+ description: '填充输入框(直接设置值)',
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ selector: { type: 'string', description: '元素选择器' },
98
+ text: { type: 'string', description: '要填充的文本' },
99
+ },
100
+ required: ['selector', 'text'],
101
+ },
102
+ },
103
+ {
104
+ name: 'browser_hover',
105
+ description: '悬停在元素上',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: { selector: { type: 'string' } },
109
+ required: ['selector'],
110
+ },
111
+ },
112
+ {
113
+ name: 'browser_press',
114
+ description: '按下键盘按键',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: { key: { type: 'string', description: '按键名称' } },
118
+ required: ['key'],
119
+ },
120
+ },
121
+ {
122
+ name: 'browser_select',
123
+ description: '选择下拉选项',
124
+ inputSchema: {
125
+ type: 'object',
126
+ properties: {
127
+ selector: { type: 'string' },
128
+ value: { type: 'string' },
129
+ },
130
+ required: ['selector', 'value'],
131
+ },
132
+ },
133
+ // 信息获取工具
134
+ {
135
+ name: 'browser_snapshot',
136
+ description: '获取页面快照(可访问性树,最适合 AI)',
137
+ inputSchema: { type: 'object', properties: {} },
138
+ },
139
+ {
140
+ name: 'browser_get_text',
141
+ description: '获取元素文本内容',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: { selector: { type: 'string' } },
145
+ required: ['selector'],
146
+ },
147
+ },
148
+ {
149
+ name: 'browser_get_html',
150
+ description: '获取元素 HTML',
151
+ inputSchema: {
152
+ type: 'object',
153
+ properties: { selector: { type: 'string' } },
154
+ required: ['selector'],
155
+ },
156
+ },
157
+ {
158
+ name: 'browser_screenshot',
159
+ description: '截取页面截图',
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: {
163
+ path: { type: 'string', description: '保存路径' },
164
+ fullPage: { type: 'boolean' },
165
+ },
166
+ },
167
+ },
168
+ {
169
+ name: 'browser_scroll',
170
+ description: '滚动页面',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: {
174
+ direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
175
+ amount: { type: 'number' },
176
+ },
177
+ required: ['direction'],
178
+ },
179
+ },
180
+ ];
181
+
182
+ /**
183
+ * 处理工具调用
184
+ */
185
+ async function handleToolCall(
186
+ name: string,
187
+ args: Record<string, unknown>
188
+ ): Promise<string> {
189
+ // 合并全局选项和 CDP 配置
190
+ const opts: SessionOptions = { ...globalOptions };
191
+ if (appConfig?.cdp.enabled && appConfig.cdp.endpoint) {
192
+ opts.cdp = appConfig.cdp.endpoint;
193
+ }
194
+
195
+ switch (name) {
196
+ // 导航
197
+ case 'browser_open':
198
+ return JSON.stringify(await navigation.open(args.url as string, opts));
199
+ case 'browser_back':
200
+ return JSON.stringify(await navigation.back(opts));
201
+ case 'browser_forward':
202
+ return JSON.stringify(await navigation.forward(opts));
203
+ case 'browser_reload':
204
+ return JSON.stringify(await navigation.reload(opts));
205
+ case 'browser_get_url':
206
+ return JSON.stringify(await navigation.getUrl(opts));
207
+ case 'browser_get_title':
208
+ return JSON.stringify(await navigation.getTitle(opts));
209
+
210
+ // 交互
211
+ case 'browser_click':
212
+ return JSON.stringify(await interaction.click(args.selector as string, opts));
213
+ case 'browser_type':
214
+ return JSON.stringify(await interaction.type(args.selector as string, args.text as string, opts));
215
+ case 'browser_fill':
216
+ return JSON.stringify(await interaction.fill(args.selector as string, args.text as string, opts));
217
+ case 'browser_hover':
218
+ return JSON.stringify(await interaction.hover(args.selector as string, opts));
219
+ case 'browser_press':
220
+ return JSON.stringify(await interaction.press(args.key as string, opts));
221
+ case 'browser_select':
222
+ return JSON.stringify(await interaction.select(args.selector as string, args.value as string, opts));
223
+
224
+ // 信息获取
225
+ case 'browser_snapshot':
226
+ return JSON.stringify(await information.snapshot(opts));
227
+ case 'browser_get_text':
228
+ return JSON.stringify(await information.getText(args.selector as string, opts));
229
+ case 'browser_get_html':
230
+ return JSON.stringify(await information.getHtml(args.selector as string, opts));
231
+
232
+ // 高级功能
233
+ case 'browser_screenshot':
234
+ return JSON.stringify(await advanced.screenshot(args.path as string, opts));
235
+ case 'browser_scroll':
236
+ return JSON.stringify(await advanced.scroll(
237
+ args.direction as 'up' | 'down' | 'left' | 'right',
238
+ args.amount as number,
239
+ opts
240
+ ));
241
+
242
+ default:
243
+ return JSON.stringify({ success: false, error: `未知工具: ${name}` });
244
+ }
245
+ }
246
+
247
+ /**
248
+ * 创建并启动 MCP 服务器
249
+ */
250
+ export async function createServer() {
251
+ const server = new Server(
252
+ { name: 'agent-browser-mcp', version: '1.0.0' },
253
+ { capabilities: { tools: {} } }
254
+ );
255
+
256
+ // 列出工具
257
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
258
+ return { tools };
259
+ });
260
+
261
+ // 调用工具
262
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
263
+ const { name, arguments: args } = request.params;
264
+ try {
265
+ const result = await handleToolCall(name, args || {});
266
+ return { content: [{ type: 'text', text: result }] };
267
+ } catch (error) {
268
+ const msg = error instanceof Error ? error.message : String(error);
269
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: msg }) }] };
270
+ }
271
+ });
272
+
273
+ return server;
274
+ }
275
+
276
+ /**
277
+ * 启动服务器
278
+ */
279
+ export async function startServer() {
280
+ appConfig = getConfig();
281
+
282
+ // 如果启用了 CDP,设置到全局选项
283
+ if (appConfig.cdp.enabled && appConfig.cdp.endpoint) {
284
+ globalOptions.cdp = appConfig.cdp.endpoint;
285
+ console.error(`[CDP] 已启用远程连接: ${appConfig.cdp.endpoint}`);
286
+ }
287
+
288
+ const server = await createServer();
289
+
290
+ if (appConfig.server.transport === 'sse') {
291
+ await startSseServer(server, appConfig);
292
+ } else {
293
+ await startStdioServer(server);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * 启动 Stdio 模式服务器
299
+ */
300
+ async function startStdioServer(server: Server) {
301
+ console.error('[服务器] 以 Stdio 模式启动');
302
+ const transport = new StdioServerTransport();
303
+ await server.connect(transport);
304
+ }
305
+
306
+ /**
307
+ * 启动 SSE 模式服务器
308
+ */
309
+ async function startSseServer(server: Server, config: AppConfig) {
310
+ const { port, host } = config.server;
311
+
312
+ // 存储活跃的 SSE 传输连接
313
+ const transports = new Map<string, SSEServerTransport>();
314
+
315
+ const httpServer = createHttpServer(async (req, res) => {
316
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
317
+
318
+ // 设置 CORS 头
319
+ res.setHeader('Access-Control-Allow-Origin', '*');
320
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
321
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
322
+
323
+ if (req.method === 'OPTIONS') {
324
+ res.writeHead(200);
325
+ res.end();
326
+ return;
327
+ }
328
+
329
+ // SSE 端点
330
+ if (url.pathname === '/sse' && req.method === 'GET') {
331
+ const transport = new SSEServerTransport('/messages', res);
332
+ const sessionId = crypto.randomUUID();
333
+ transports.set(sessionId, transport);
334
+
335
+ console.error(`[SSE] 新连接: ${sessionId}`);
336
+
337
+ res.on('close', () => {
338
+ transports.delete(sessionId);
339
+ console.error(`[SSE] 连接关闭: ${sessionId}`);
340
+ });
341
+
342
+ await server.connect(transport);
343
+ return;
344
+ }
345
+
346
+ // 消息端点
347
+ if (url.pathname === '/messages' && req.method === 'POST') {
348
+ const sessionId = url.searchParams.get('sessionId');
349
+ if (!sessionId) {
350
+ res.writeHead(400, { 'Content-Type': 'application/json' });
351
+ res.end(JSON.stringify({ error: '缺少 sessionId 参数' }));
352
+ return;
353
+ }
354
+
355
+ const transport = transports.get(sessionId);
356
+ if (!transport) {
357
+ res.writeHead(404, { 'Content-Type': 'application/json' });
358
+ res.end(JSON.stringify({ error: '会话不存在' }));
359
+ return;
360
+ }
361
+
362
+ await transport.handlePostMessage(req, res);
363
+ return;
364
+ }
365
+
366
+ // 健康检查端点
367
+ if (url.pathname === '/health') {
368
+ res.writeHead(200, { 'Content-Type': 'application/json' });
369
+ res.end(JSON.stringify({
370
+ status: 'ok',
371
+ transport: 'sse',
372
+ connections: transports.size
373
+ }));
374
+ return;
375
+ }
376
+
377
+ // 404
378
+ res.writeHead(404, { 'Content-Type': 'application/json' });
379
+ res.end(JSON.stringify({ error: 'Not Found' }));
380
+ });
381
+
382
+ httpServer.listen(port, host, () => {
383
+ console.error(`[服务器] SSE 模式已启动`);
384
+ console.error(`[服务器] 监听地址: http://${host}:${port}`);
385
+ console.error(`[服务器] SSE 端点: http://${host}:${port}/sse`);
386
+ console.error(`[服务器] 健康检查: http://${host}:${port}/health`);
387
+ });
388
+ }
@@ -0,0 +1,27 @@
1
+ import { executeCommand } from './utils/executor.js';
2
+
3
+ async function test() {
4
+ console.log('测试 agent-browser CLI 命令执行...\n');
5
+
6
+ // 测试打开页面
7
+ console.log('1. 测试打开百度...');
8
+ const openResult = await executeCommand(['open', 'https://www.baidu.com']);
9
+ console.log('结果:', openResult);
10
+
11
+ // 测试获取标题
12
+ console.log('\n2. 测试获取页面标题...');
13
+ const titleResult = await executeCommand(['get', 'title']);
14
+ console.log('结果:', titleResult);
15
+
16
+ // 测试获取快照
17
+ console.log('\n3. 测试获取页面快照...');
18
+ const snapshotResult = await executeCommand(['snapshot']);
19
+ console.log('结果:', snapshotResult.success ? '成功' : snapshotResult.error);
20
+ if (snapshotResult.success) {
21
+ console.log('快照长度:', snapshotResult.output.length, '字符');
22
+ }
23
+
24
+ console.log('\n测试完成!');
25
+ }
26
+
27
+ test().catch(console.error);
package/src/test.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { createServer } from './server.js';
2
+
3
+ async function test() {
4
+ console.log('创建 MCP 服务器...');
5
+ const server = await createServer();
6
+ console.log('服务器创建成功');
7
+
8
+ // 测试列出工具
9
+ console.log('\n测试列出工具...');
10
+ const toolsHandler = (server as any)._requestHandlers?.get('tools/list');
11
+ if (toolsHandler) {
12
+ const result = await toolsHandler({});
13
+ console.log(`找到 ${result.tools.length} 个工具:`);
14
+ result.tools.forEach((t: any) => console.log(` - ${t.name}: ${t.description}`));
15
+ }
16
+
17
+ console.log('\n测试完成!');
18
+ }
19
+
20
+ test().catch(console.error);
@@ -0,0 +1,55 @@
1
+ import { executeCommand, ExecuteOptions } from '../utils/executor.js';
2
+
3
+ /**
4
+ * 高级功能工具
5
+ */
6
+
7
+ /** 截图 */
8
+ export async function screenshot(
9
+ path?: string,
10
+ options?: ExecuteOptions & { fullPage?: boolean }
11
+ ) {
12
+ const args = ['screenshot'];
13
+ if (path) {
14
+ args.push(path);
15
+ }
16
+ if (options?.fullPage) {
17
+ args.push('--full-page');
18
+ }
19
+ return executeCommand(args, options);
20
+ }
21
+
22
+ /** 滚动页面 */
23
+ export async function scroll(
24
+ direction: 'up' | 'down' | 'left' | 'right',
25
+ amount?: number,
26
+ options?: ExecuteOptions
27
+ ) {
28
+ const args = ['scroll', direction];
29
+ if (amount !== undefined) {
30
+ args.push(String(amount));
31
+ }
32
+ return executeCommand(args, options);
33
+ }
34
+
35
+ /** 滚动到元素 */
36
+ export async function scrollTo(selector: string, options?: ExecuteOptions) {
37
+ return executeCommand(['scroll', 'to', selector], options);
38
+ }
39
+
40
+ /** 等待元素出现 */
41
+ export async function waitFor(
42
+ selector: string,
43
+ options?: ExecuteOptions & { state?: 'visible' | 'hidden' | 'attached' }
44
+ ) {
45
+ const args = ['wait', 'for', selector];
46
+ if (options?.state) {
47
+ args.push('--state', options.state);
48
+ }
49
+ return executeCommand(args, options);
50
+ }
51
+
52
+ /** 执行 JavaScript */
53
+ export async function evaluate(script: string, options?: ExecuteOptions) {
54
+ return executeCommand(['eval', script], options);
55
+ }
@@ -0,0 +1,54 @@
1
+ import { executeCommand, ExecuteOptions } from '../utils/executor.js';
2
+
3
+ /**
4
+ * 信息获取相关工具
5
+ */
6
+
7
+ /** 获取页面快照(可访问性树) */
8
+ export async function snapshot(options?: ExecuteOptions) {
9
+ return executeCommand(['snapshot'], options);
10
+ }
11
+
12
+ /** 获取元素文本 */
13
+ export async function getText(selector: string, options?: ExecuteOptions) {
14
+ return executeCommand(['get', 'text', selector], options);
15
+ }
16
+
17
+ /** 获取元素 HTML */
18
+ export async function getHtml(selector: string, options?: ExecuteOptions) {
19
+ return executeCommand(['get', 'html', selector], options);
20
+ }
21
+
22
+ /** 获取输入框的值 */
23
+ export async function getValue(selector: string, options?: ExecuteOptions) {
24
+ return executeCommand(['get', 'value', selector], options);
25
+ }
26
+
27
+ /** 获取元素属性 */
28
+ export async function getAttribute(
29
+ selector: string,
30
+ attribute: string,
31
+ options?: ExecuteOptions
32
+ ) {
33
+ return executeCommand(['get', 'attr', selector, attribute], options);
34
+ }
35
+
36
+ /** 获取元素数量 */
37
+ export async function count(selector: string, options?: ExecuteOptions) {
38
+ return executeCommand(['count', selector], options);
39
+ }
40
+
41
+ /** 检查元素是否可见 */
42
+ export async function isVisible(selector: string, options?: ExecuteOptions) {
43
+ return executeCommand(['visible', selector], options);
44
+ }
45
+
46
+ /** 检查元素是否启用 */
47
+ export async function isEnabled(selector: string, options?: ExecuteOptions) {
48
+ return executeCommand(['enabled', selector], options);
49
+ }
50
+
51
+ /** 检查复选框是否选中 */
52
+ export async function isChecked(selector: string, options?: ExecuteOptions) {
53
+ return executeCommand(['checked', selector], options);
54
+ }
@@ -0,0 +1,74 @@
1
+ import { executeCommand, ExecuteOptions } from '../utils/executor.js';
2
+
3
+ /**
4
+ * 交互相关工具
5
+ */
6
+
7
+ /** 点击元素 */
8
+ export async function click(
9
+ selector: string,
10
+ options?: ExecuteOptions & { button?: 'left' | 'right' | 'middle' }
11
+ ) {
12
+ const args = ['click', selector];
13
+ if (options?.button) {
14
+ args.push('--button', options.button);
15
+ }
16
+ return executeCommand(args, options);
17
+ }
18
+
19
+ /** 双击元素 */
20
+ export async function dblclick(selector: string, options?: ExecuteOptions) {
21
+ return executeCommand(['dblclick', selector], options);
22
+ }
23
+
24
+ /** 悬停在元素上 */
25
+ export async function hover(selector: string, options?: ExecuteOptions) {
26
+ return executeCommand(['hover', selector], options);
27
+ }
28
+
29
+ /** 输入文本(逐字符) */
30
+ export async function type(
31
+ selector: string,
32
+ text: string,
33
+ options?: ExecuteOptions
34
+ ) {
35
+ return executeCommand(['type', selector, text], options);
36
+ }
37
+
38
+ /** 填充文本(直接设置值) */
39
+ export async function fill(
40
+ selector: string,
41
+ text: string,
42
+ options?: ExecuteOptions
43
+ ) {
44
+ return executeCommand(['fill', selector, text], options);
45
+ }
46
+
47
+ /** 清空输入框 */
48
+ export async function clear(selector: string, options?: ExecuteOptions) {
49
+ return executeCommand(['clear', selector], options);
50
+ }
51
+
52
+ /** 按键 */
53
+ export async function press(key: string, options?: ExecuteOptions) {
54
+ return executeCommand(['press', key], options);
55
+ }
56
+
57
+ /** 选择下拉选项 */
58
+ export async function select(
59
+ selector: string,
60
+ value: string,
61
+ options?: ExecuteOptions
62
+ ) {
63
+ return executeCommand(['select', selector, value], options);
64
+ }
65
+
66
+ /** 勾选复选框 */
67
+ export async function check(selector: string, options?: ExecuteOptions) {
68
+ return executeCommand(['check', selector], options);
69
+ }
70
+
71
+ /** 取消勾选复选框 */
72
+ export async function uncheck(selector: string, options?: ExecuteOptions) {
73
+ return executeCommand(['uncheck', selector], options);
74
+ }
@@ -0,0 +1,51 @@
1
+ import { executeCommand, ExecuteOptions } from '../utils/executor.js';
2
+
3
+ /**
4
+ * 导航相关工具
5
+ */
6
+
7
+ /** 打开 URL */
8
+ export async function open(url: string, options?: ExecuteOptions) {
9
+ return executeCommand(['open', url], options);
10
+ }
11
+
12
+ /** 后退 */
13
+ export async function back(options?: ExecuteOptions) {
14
+ return executeCommand(['back'], options);
15
+ }
16
+
17
+ /** 前进 */
18
+ export async function forward(options?: ExecuteOptions) {
19
+ return executeCommand(['forward'], options);
20
+ }
21
+
22
+ /** 刷新页面 */
23
+ export async function reload(options?: ExecuteOptions) {
24
+ return executeCommand(['reload'], options);
25
+ }
26
+
27
+ /** 获取当前 URL */
28
+ export async function getUrl(options?: ExecuteOptions) {
29
+ return executeCommand(['get', 'url'], options);
30
+ }
31
+
32
+ /** 获取页面标题 */
33
+ export async function getTitle(options?: ExecuteOptions) {
34
+ return executeCommand(['get', 'title'], options);
35
+ }
36
+
37
+ /** 等待页面加载 */
38
+ export async function waitForLoad(
39
+ state: 'load' | 'domcontentloaded' | 'networkidle' = 'load',
40
+ options?: ExecuteOptions
41
+ ) {
42
+ return executeCommand(['wait', 'load', state], options);
43
+ }
44
+
45
+ /** 等待 URL 匹配 */
46
+ export async function waitForUrl(
47
+ pattern: string,
48
+ options?: ExecuteOptions
49
+ ) {
50
+ return executeCommand(['wait', 'url', pattern], options);
51
+ }