rust-rpa 0.1.7 → 0.2.0-beta.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.
package/bin/rpa.js CHANGED
@@ -28,7 +28,9 @@ function printTable(rows, keys) {
28
28
  }
29
29
 
30
30
  function findWindow(target) {
31
- const win = Window.all().find(w =>
31
+ // 获取所有窗口,按面积从大到小排序,优先匹配大窗口
32
+ const windows = Window.all().sort((a, b) => (b.width() * b.height()) - (a.width() * a.height()));
33
+ const win = windows.find(w =>
32
34
  String(w.id()) === target || w.appName().includes(target) || w.title().includes(target)
33
35
  );
34
36
  if (!win) die(`未找到匹配 "${target}" 的窗口`);
@@ -247,5 +249,518 @@ clipboard.command('paste-file').description('粘贴文件(不影响剪贴板
247
249
  console.log(c.green(`已粘贴文件: ${paths.join(', ')}(剪贴板已恢复)`));
248
250
  });
249
251
 
252
+ // ── image ──────────────────────────────────────────────────────────────────
253
+ const image = program.command('image').description('图片处理');
254
+
255
+ image.command('info').description('查看图片信息')
256
+ .argument('<file>', '图片文件路径')
257
+ .action(async (file) => {
258
+ const img = await ImageData.fromFile(file);
259
+ const meta = img.metadata();
260
+ console.log(`文件: ${file}`);
261
+ console.log(`尺寸: ${meta.width}x${meta.height}`);
262
+ console.log(`通道: ${meta.channels} (RGBA)`);
263
+ console.log(`数据大小: ${meta.dataSize} bytes`);
264
+ });
265
+
266
+ image.command('crop').description('裁剪图片')
267
+ .argument('<file>', '图片文件路径')
268
+ .argument('<x>', '裁剪区域 X 坐标')
269
+ .argument('<y>', '裁剪区域 Y 坐标')
270
+ .argument('<width>', '裁剪区域宽度')
271
+ .argument('<height>', '裁剪区域高度')
272
+ .option('-o, --output <file>', '输出文件路径', 'cropped.png')
273
+ .action(async (file, x, y, width, height, { output }) => {
274
+ const img = await ImageData.fromFile(file);
275
+ const cropped = await img.crop(+x, +y, +width, +height);
276
+ await cropped.toFile(output);
277
+ console.log(c.green(`已裁剪: ${output} (${cropped.width}x${cropped.height})`));
278
+ });
279
+
280
+ image.command('resize').description('缩放图片')
281
+ .argument('<file>', '图片文件路径')
282
+ .argument('<width>', '目标宽度')
283
+ .argument('<height>', '目标高度')
284
+ .option('-o, --output <file>', '输出文件路径', 'resized.png')
285
+ .action(async (file, width, height, { output }) => {
286
+ const img = await ImageData.fromFile(file);
287
+ const resized = await img.resize(+width, +height);
288
+ await resized.toFile(output);
289
+ console.log(c.green(`已缩放: ${output} (${resized.width}x${resized.height})`));
290
+ });
291
+
292
+ image.command('grayscale').description('转换为灰度图')
293
+ .argument('<file>', '图片文件路径')
294
+ .option('-o, --output <file>', '输出文件路径', 'gray.png')
295
+ .action(async (file, { output }) => {
296
+ const img = await ImageData.fromFile(file);
297
+ const gray = await img.grayscale();
298
+ await gray.toFile(output);
299
+ console.log(c.green(`已转换灰度图: ${output}`));
300
+ });
301
+
302
+ image.command('find-text').description('在图片中查找文字(OCR)')
303
+ .argument('<file>', '图片文件路径')
304
+ .argument('<text>', '要查找的文字')
305
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
306
+ .action(async (file, text, { region }) => {
307
+ const img = await ImageData.fromFile(file);
308
+ const options = {};
309
+ if (region) {
310
+ const [x, y, w, h] = region.split(',').map(Number);
311
+ options.regions = [{ x, y, width: w, height: h }];
312
+ }
313
+ const result = await img.findText(text, options);
314
+ if (result) {
315
+ console.log(c.green(`找到文字: "${result.text}"`));
316
+ console.log(`位置: (${result.x}, ${result.y})`);
317
+ console.log(`大小: ${result.width}x${result.height}`);
318
+ console.log(`置信度: ${(result.confidence * 100).toFixed(2)}%`);
319
+ } else {
320
+ console.log(c.yellow('未找到文字'));
321
+ process.exit(1);
322
+ }
323
+ });
324
+
325
+ image.command('recognize-text').description('识别图片中的所有文字(OCR)')
326
+ .argument('<file>', '图片文件路径')
327
+ .option('--json', '输出 JSON 格式')
328
+ .action(async (file, { json }) => {
329
+ const img = await ImageData.fromFile(file);
330
+ const results = await img.recognizeText();
331
+ if (json) {
332
+ console.log(JSON.stringify(results, null, 2));
333
+ } else {
334
+ console.log(c.green(`识别到 ${results.length} 处文字:`));
335
+ results.forEach((r, i) => {
336
+ console.log(`\n${i + 1}. "${r.text}"`);
337
+ console.log(` 位置: (${r.x}, ${r.y}) 大小: ${r.width}x${r.height}`);
338
+ console.log(` 置信度: ${(r.confidence * 100).toFixed(2)}%`);
339
+ });
340
+ }
341
+ });
342
+
343
+ image.command('find-icon').description('在图片中查找图标')
344
+ .argument('<file>', '图片文件路径')
345
+ .argument('<template>', '模板图片路径')
346
+ .option('--threshold <n>', '匹配阈值 (0.0-1.0)', '0.8')
347
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
348
+ .action(async (file, template, { threshold, region }) => {
349
+ const img = await ImageData.fromFile(file);
350
+ const tmpl = await ImageData.fromFile(template);
351
+ const options = { threshold: +threshold };
352
+ if (region) {
353
+ const [x, y, w, h] = region.split(',').map(Number);
354
+ options.regions = [{ x, y, width: w, height: h }];
355
+ }
356
+ const result = await img.findIcon(tmpl, options);
357
+ if (result.found) {
358
+ console.log(c.green(`找到图标`));
359
+ console.log(`位置: (${result.x}, ${result.y})`);
360
+ console.log(`大小: ${result.width}x${result.height}`);
361
+ console.log(`相似度: ${(result.score * 100).toFixed(2)}%`);
362
+ } else {
363
+ console.log(c.yellow('未找到图标'));
364
+ console.log(`最高相似度: ${(result.score * 100).toFixed(2)}%`);
365
+ process.exit(1);
366
+ }
367
+ });
368
+
369
+ // ── monitor ocr ─────────────────────────────────────────────────────────────
370
+ monitor.command('find-text').description('在显示器截图中查找文字(OCR)')
371
+ .argument('<text>', '要查找的文字')
372
+ .option('--monitor <id>', '指定显示器 ID')
373
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
374
+ .action(async (text, { monitor: monitorId, region }) => {
375
+ const monitors = Monitor.all();
376
+ let mon;
377
+ if (monitorId) {
378
+ mon = monitors.find(m => String(m.id()) === monitorId);
379
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
380
+ } else {
381
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
382
+ }
383
+ const options = {};
384
+ if (region) {
385
+ const [x, y, w, h] = region.split(',').map(Number);
386
+ options.regions = [{ x, y, width: w, height: h }];
387
+ }
388
+ const result = await mon.findText(text, options);
389
+ if (result) {
390
+ console.log(c.green(`找到文字: "${result.text}"`));
391
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
392
+ console.log(`大小: ${result.width}x${result.height}`);
393
+ } else {
394
+ console.log(c.yellow('未找到文字'));
395
+ process.exit(1);
396
+ }
397
+ });
398
+
399
+ monitor.command('recognize-text').description('识别显示器截图中的所有文字(OCR)')
400
+ .option('--monitor <id>', '指定显示器 ID')
401
+ .option('--json', '输出 JSON 格式')
402
+ .action(async ({ monitor: monitorId, json }) => {
403
+ const monitors = Monitor.all();
404
+ let mon;
405
+ if (monitorId) {
406
+ mon = monitors.find(m => String(m.id()) === monitorId);
407
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
408
+ } else {
409
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
410
+ }
411
+ const results = await mon.recognizeText();
412
+ if (json) {
413
+ console.log(JSON.stringify(results, null, 2));
414
+ } else {
415
+ console.log(c.green(`识别到 ${results.length} 处文字:`));
416
+ results.forEach((r, i) => {
417
+ console.log(`\n${i + 1}. "${r.text}"`);
418
+ console.log(` 屏幕位置: (${r.x}, ${r.y}) 大小: ${r.width}x${r.height}`);
419
+ });
420
+ }
421
+ });
422
+
423
+ monitor.command('find-icon').description('在显示器截图中查找图标')
424
+ .argument('<template>', '模板图片路径')
425
+ .option('--monitor <id>', '指定显示器 ID')
426
+ .option('--threshold <n>', '匹配阈值 (0.0-1.0)', '0.8')
427
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
428
+ .action(async (template, { monitor: monitorId, threshold, region }) => {
429
+ const monitors = Monitor.all();
430
+ let mon;
431
+ if (monitorId) {
432
+ mon = monitors.find(m => String(m.id()) === monitorId);
433
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
434
+ } else {
435
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
436
+ }
437
+ const tmpl = await ImageData.fromFile(template);
438
+ const options = { threshold: +threshold };
439
+ if (region) {
440
+ const [x, y, w, h] = region.split(',').map(Number);
441
+ options.regions = [{ x, y, width: w, height: h }];
442
+ }
443
+ const result = await mon.findIcon(tmpl, options);
444
+ if (result.found) {
445
+ console.log(c.green(`找到图标`));
446
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
447
+ console.log(`大小: ${result.width}x${result.height}`);
448
+ console.log(`相似度: ${(result.score * 100).toFixed(2)}%`);
449
+ } else {
450
+ console.log(c.yellow('未找到图标'));
451
+ process.exit(1);
452
+ }
453
+ });
454
+
455
+ monitor.command('wait-text').description('等待指定文字出现(OCR)')
456
+ .argument('<text>', '要等待的文字')
457
+ .option('--monitor <id>', '指定显示器 ID')
458
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
459
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
460
+ .action(async (text, { monitor: monitorId, timeout, region }) => {
461
+ const monitors = Monitor.all();
462
+ let mon;
463
+ if (monitorId) {
464
+ mon = monitors.find(m => String(m.id()) === monitorId);
465
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
466
+ } else {
467
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
468
+ }
469
+ const options = { timeout: +timeout };
470
+ if (region) {
471
+ const [x, y, w, h] = region.split(',').map(Number);
472
+ options.regions = [{ x, y, width: w, height: h }];
473
+ }
474
+ console.log(`等待文字 "${text}" 出现(超时: ${timeout}ms)...`);
475
+ const result = await mon.waitText(text, options);
476
+ console.log(c.green(`找到文字: "${result.text}"`));
477
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
478
+ });
479
+
480
+ monitor.command('wait-icon').description('等待指定图标出现')
481
+ .argument('<template>', '模板图片路径')
482
+ .option('--monitor <id>', '指定显示器 ID')
483
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
484
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
485
+ .action(async (template, { monitor: monitorId, timeout, region }) => {
486
+ const monitors = Monitor.all();
487
+ let mon;
488
+ if (monitorId) {
489
+ mon = monitors.find(m => String(m.id()) === monitorId);
490
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
491
+ } else {
492
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
493
+ }
494
+ const tmpl = await ImageData.fromFile(template);
495
+ const options = { timeout: +timeout };
496
+ if (region) {
497
+ const [x, y, w, h] = region.split(',').map(Number);
498
+ options.regions = [{ x, y, width: w, height: h }];
499
+ }
500
+ console.log(`等待图标出现(超时: ${timeout}ms)...`);
501
+ const result = await mon.waitIcon(tmpl, options);
502
+ console.log(c.green(`找到图标`));
503
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
504
+ });
505
+
506
+ monitor.command('click-text').description('点击指定文字(OCR)')
507
+ .argument('<text>', '要点击的文字')
508
+ .option('--monitor <id>', '指定显示器 ID')
509
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
510
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
511
+ .action(async (text, { monitor: monitorId, timeout, region }) => {
512
+ const monitors = Monitor.all();
513
+ let mon;
514
+ if (monitorId) {
515
+ mon = monitors.find(m => String(m.id()) === monitorId);
516
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
517
+ } else {
518
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
519
+ }
520
+ const options = { timeout: +timeout };
521
+ if (region) {
522
+ const [x, y, w, h] = region.split(',').map(Number);
523
+ options.regions = [{ x, y, width: w, height: h }];
524
+ }
525
+ console.log(`等待并点击文字 "${text}"(超时: ${timeout}ms)...`);
526
+ await mon.clickText(text, options);
527
+ console.log(c.green(`已点击文字`));
528
+ });
529
+
530
+ monitor.command('click-icon').description('点击指定图标')
531
+ .argument('<template>', '模板图片路径')
532
+ .option('--monitor <id>', '指定显示器 ID')
533
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
534
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
535
+ .action(async (template, { monitor: monitorId, timeout, region }) => {
536
+ const monitors = Monitor.all();
537
+ let mon;
538
+ if (monitorId) {
539
+ mon = monitors.find(m => String(m.id()) === monitorId);
540
+ if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
541
+ } else {
542
+ mon = monitors.find(m => m.isPrimary()) || monitors[0];
543
+ }
544
+ const tmpl = await ImageData.fromFile(template);
545
+ const options = { timeout: +timeout };
546
+ if (region) {
547
+ const [x, y, w, h] = region.split(',').map(Number);
548
+ options.regions = [{ x, y, width: w, height: h }];
549
+ }
550
+ console.log(`等待并点击图标(超时: ${timeout}ms)...`);
551
+ await mon.clickIcon(tmpl, options);
552
+ console.log(c.green(`已点击图标`));
553
+ });
554
+
555
+ // ── window ocr ──────────────────────────────────────────────────────────────
556
+ window.command('capture').description('截取窗口截图')
557
+ .argument('<target>', '按 appName/title/id 匹配')
558
+ .option('-o, --output <file>', '输出文件路径', 'window-capture.png')
559
+ .action(async (target, { output }) => {
560
+ const win = findWindow(target);
561
+ console.log(`截取窗口: ${win.appName()} - ${win.title()}`);
562
+ const image = await win.captureImage();
563
+ await image.toFile(output);
564
+ console.log(c.green(`已保存: ${output} (${image.width}x${image.height})`));
565
+ });
566
+
567
+ window.command('find-text').description('在窗口截图中查找文字(OCR)')
568
+ .argument('<target>', '按 appName/title/id 匹配')
569
+ .argument('<text>', '要查找的文字')
570
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
571
+ .action(async (target, text, { region }) => {
572
+ const win = findWindow(target);
573
+ const options = {};
574
+ if (region) {
575
+ const [x, y, w, h] = region.split(',').map(Number);
576
+ options.regions = [{ x, y, width: w, height: h }];
577
+ }
578
+ const result = await win.findText(text, options);
579
+ if (result) {
580
+ console.log(c.green(`找到文字: "${result.text}"`));
581
+ console.log(`窗口内位置: (${result.x}, ${result.y})`);
582
+ console.log(`大小: ${result.width}x${result.height}`);
583
+ } else {
584
+ console.log(c.yellow('未找到文字'));
585
+ process.exit(1);
586
+ }
587
+ });
588
+
589
+ window.command('recognize-text').description('识别窗口截图中的所有文字(OCR)')
590
+ .argument('<target>', '按 appName/title/id 匹配')
591
+ .option('--json', '输出 JSON 格式')
592
+ .action(async (target, { json }) => {
593
+ const win = findWindow(target);
594
+ const results = await win.recognizeText();
595
+ if (json) {
596
+ console.log(JSON.stringify(results, null, 2));
597
+ } else {
598
+ console.log(c.green(`识别到 ${results.length} 处文字:`));
599
+ results.forEach((r, i) => {
600
+ console.log(`\n${i + 1}. "${r.text}"`);
601
+ console.log(` 窗口内位置: (${r.x}, ${r.y}) 大小: ${r.width}x${r.height}`);
602
+ });
603
+ }
604
+ });
605
+
606
+ window.command('find-icon').description('在窗口截图中查找图标')
607
+ .argument('<target>', '按 appName/title/id 匹配')
608
+ .argument('<template>', '模板图片路径')
609
+ .option('--threshold <n>', '匹配阈值 (0.0-1.0)', '0.8')
610
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
611
+ .action(async (target, template, { threshold, region }) => {
612
+ const win = findWindow(target);
613
+ const tmpl = await ImageData.fromFile(template);
614
+ const options = { threshold: +threshold };
615
+ if (region) {
616
+ const [x, y, w, h] = region.split(',').map(Number);
617
+ options.regions = [{ x, y, width: w, height: h }];
618
+ }
619
+ const result = await win.findIcon(tmpl, options);
620
+ if (result.found) {
621
+ console.log(c.green(`找到图标`));
622
+ console.log(`窗口内位置: (${result.x}, ${result.y})`);
623
+ console.log(`大小: ${result.width}x${result.height}`);
624
+ console.log(`相似度: ${(result.score * 100).toFixed(2)}%`);
625
+ } else {
626
+ console.log(c.yellow('未找到图标'));
627
+ process.exit(1);
628
+ }
629
+ });
630
+
631
+ window.command('wait-text').description('等待窗口内指定文字出现(OCR)')
632
+ .argument('<target>', '按 appName/title/id 匹配')
633
+ .argument('<text>', '要等待的文字')
634
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
635
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
636
+ .action(async (target, text, { timeout, region }) => {
637
+ const win = findWindow(target);
638
+ const options = { timeout: +timeout };
639
+ if (region) {
640
+ const [x, y, w, h] = region.split(',').map(Number);
641
+ options.regions = [{ x, y, width: w, height: h }];
642
+ }
643
+ console.log(`等待文字 "${text}" 出现(超时: ${timeout}ms)...`);
644
+ const result = await win.waitText(text, options);
645
+ console.log(c.green(`找到文字: "${result.text}"`));
646
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
647
+ });
648
+
649
+ window.command('wait-icon').description('等待窗口内指定图标出现')
650
+ .argument('<target>', '按 appName/title/id 匹配')
651
+ .argument('<template>', '模板图片路径')
652
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
653
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
654
+ .action(async (target, template, { timeout, region }) => {
655
+ const win = findWindow(target);
656
+ const tmpl = await ImageData.fromFile(template);
657
+ const options = { timeout: +timeout };
658
+ if (region) {
659
+ const [x, y, w, h] = region.split(',').map(Number);
660
+ options.regions = [{ x, y, width: w, height: h }];
661
+ }
662
+ console.log(`等待图标出现(超时: ${timeout}ms)...`);
663
+ const result = await win.waitIcon(tmpl, options);
664
+ console.log(c.green(`找到图标`));
665
+ console.log(`屏幕位置: (${result.x}, ${result.y})`);
666
+ });
667
+
668
+ window.command('click-text').description('点击窗口内指定文字(OCR)')
669
+ .argument('<target>', '按 appName/title/id 匹配')
670
+ .argument('<text>', '要点击的文字')
671
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
672
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
673
+ .action(async (target, text, { timeout, region }) => {
674
+ const win = findWindow(target);
675
+ const options = { timeout: +timeout };
676
+ if (region) {
677
+ const [x, y, w, h] = region.split(',').map(Number);
678
+ options.regions = [{ x, y, width: w, height: h }];
679
+ }
680
+ console.log(`等待并点击文字 "${text}"(超时: ${timeout}ms)...`);
681
+ await win.clickText(text, options);
682
+ console.log(c.green(`已点击文字`));
683
+ });
684
+
685
+ window.command('click-icon').description('点击窗口内指定图标')
686
+ .argument('<target>', '按 appName/title/id 匹配')
687
+ .argument('<template>', '模板图片路径')
688
+ .option('--timeout <ms>', '超时时间(毫秒)', '3000')
689
+ .option('--region <x,y,w,h>', '指定查找区域,格式: x,y,w,h')
690
+ .action(async (target, template, { timeout, region }) => {
691
+ const win = findWindow(target);
692
+ const tmpl = await ImageData.fromFile(template);
693
+ const options = { timeout: +timeout };
694
+ if (region) {
695
+ const [x, y, w, h] = region.split(',').map(Number);
696
+ options.regions = [{ x, y, width: w, height: h }];
697
+ }
698
+ console.log(`等待并点击图标(超时: ${timeout}ms)...`);
699
+ await win.clickIcon(tmpl, options);
700
+ console.log(c.green(`已点击图标`));
701
+ });
702
+
703
+ // ── shortcuts ───────────────────────────────────────────────────────────────
704
+ // 常用命令的顶级快捷方式(无需指定命名空间)
705
+
706
+ program.command('paste-text').description('粘贴文本(快捷方式: clipboard paste-text)')
707
+ .argument('<text...>', '要粘贴的文本')
708
+ .action(async (parts) => {
709
+ const text = parts.join(' ');
710
+ await Clipboard.pasteText(text);
711
+ console.log(c.green(`已粘贴文本: "${text}"(剪贴板已恢复)`));
712
+ });
713
+
714
+ program.command('paste').description('执行粘贴 (Cmd/Ctrl+V)(快捷方式: clipboard paste)')
715
+ .action(async () => {
716
+ await Clipboard.paste();
717
+ console.log(c.green('已执行粘贴'));
718
+ });
719
+
720
+ program.command('type').description('输入文本(快捷方式: keyboard type)')
721
+ .argument('<text...>', '要输入的文本')
722
+ .action(async (parts) => {
723
+ const text = parts.join(' ');
724
+ await Keyboard.typeText(text);
725
+ console.log(c.green(`已输入: "${text}"`));
726
+ });
727
+
728
+ program.command('press').description('按键或组合键(快捷方式: keyboard press)')
729
+ .argument('<keys>', '按键(组合键用 + 连接,如 ctrl+c)')
730
+ .action(async (raw) => {
731
+ const keys = raw.split('+').map(k => k.trim().toLowerCase());
732
+ if (keys.length === 1) await Keyboard.click(keys[0]);
733
+ else await Keyboard.sequence(keys);
734
+ console.log(c.green(`已按下: ${raw}`));
735
+ });
736
+
737
+ program.command('click').description('点击鼠标(快捷方式: mouse click)')
738
+ .argument('[button]', '按钮 (left/right/middle)', 'left')
739
+ .option('--double', '双击')
740
+ .action(async (button, { double }) => {
741
+ if (double) {
742
+ await Mouse.doubleClick(button);
743
+ console.log(c.green(`双击: ${button}`));
744
+ } else {
745
+ await Mouse.click(button);
746
+ console.log(c.green(`点击: ${button}`));
747
+ }
748
+ });
749
+
750
+ program.command('move').description('移动鼠标(快捷方式: mouse move)')
751
+ .argument('<x>', 'X 坐标').argument('<y>', 'Y 坐标')
752
+ .action(async (x, y) => {
753
+ await Mouse.moveTo(+x, +y);
754
+ console.log(c.green(`鼠标已移动到 (${x}, ${y})`));
755
+ });
756
+
757
+ program.command('pause').description('暂停/等待指定时间(毫秒)')
758
+ .argument('<ms>', '等待时间(毫秒)')
759
+ .action(async (ms) => {
760
+ const delay = parseInt(ms, 10);
761
+ await new Promise(resolve => setTimeout(resolve, delay));
762
+ console.log(c.green(`已等待 ${delay}ms`));
763
+ });
764
+
250
765
  // ── run ─────────────────────────────────────────────────────────────────────
251
766
  program.parseAsync().catch(err => die(err.message));