cortico-world-hands 0.1.0 → 0.1.2

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 (2) hide show
  1. package/dist/index.js +158 -11
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var HANDS_DEFAULTS = {
4
4
  enabled: false,
5
5
  actuator: "auto",
6
6
  eyesPort: 17898,
7
+ handPort: 0,
7
8
  sensePort: 17899,
8
9
  rootCommand: "su",
9
10
  shellCommand: "",
@@ -82,6 +83,14 @@ var HANDS_CONFIG_GROUP = {
82
83
  description: "\u622A\u5C4F\u3001\u8BFB\u5B57\u548C\u624B\u52BF\u7AEF\u70B9\u90FD\u5728\u8FD9\u4E2A\u53E3\u4E0A\uFF0C\u968F\u5305\u7684\u65E0\u969C\u788D\u670D\u52A1\u7528 17898\u3002",
83
84
  "x-hot": false
84
85
  },
86
+ [`${K}.handPort`]: {
87
+ type: "integer",
88
+ title: "\u624B\u52BF\u7AEF\u53E3",
89
+ minimum: 0,
90
+ maximum: 65535,
91
+ description: "\u72EC\u7ACB\u624B\u52BF App\uFF08\u624B\uFF09\u7684\u7AEF\u53E3\uFF0C\u9ED8\u8BA4 17897\u3002\u586B 0 \u8868\u793A\u548C\u773C\u775B\u540C\u7AEF\u53E3\u2014\u2014\u90A3\u662F\u628A /hand/* \u8865\u8FDB Coopanin \u65E0\u969C\u788D\u670D\u52A1\u7684\u88C5\u6CD5\u3002",
92
+ "x-hot": false
93
+ },
85
94
  [`${K}.sensePort`]: {
86
95
  type: "integer",
87
96
  title: "\u611F\u89C9\u7AEF\u53E3",
@@ -240,6 +249,10 @@ function inputCommand(command) {
240
249
  return command.ms >= 400 ? `input swipe ${command.x} ${command.y} ${command.x} ${command.y} ${command.ms}` : `input tap ${command.x} ${command.y}`;
241
250
  case "swipe":
242
251
  return `input swipe ${command.x1} ${command.y1} ${command.x2} ${command.y2} ${command.ms}`;
252
+ case "pinch":
253
+ return `input swipe ${command.cx - 220} ${command.cy} ${command.cx + 220} ${command.cy} ${command.ms}`;
254
+ case "drag":
255
+ return `input draganddrop ${command.x1} ${command.y1} ${command.x2} ${command.y2} ${command.ms}`;
243
256
  case "key":
244
257
  return `input keyevent ${command.code}`;
245
258
  // 文本在拼命令这一刻才转义:只有 input 这条路吃不下非 ASCII,无障碍那条能原样收
@@ -375,6 +388,10 @@ var EyesActuator = class {
375
388
  return this.send(`/hand/tap?x=${command.x}&y=${command.y}&ms=${command.ms}`);
376
389
  case "swipe":
377
390
  return this.send(`/hand/swipe?x1=${command.x1}&y1=${command.y1}&x2=${command.x2}&y2=${command.y2}&ms=${command.ms}`);
391
+ case "pinch":
392
+ return this.send(`/hand/pinch?cx=${command.cx}&cy=${command.cy}&scale=${command.scale}&ms=${command.ms}`);
393
+ case "drag":
394
+ return this.send(`/hand/drag?x1=${command.x1}&y1=${command.y1}&x2=${command.x2}&y2=${command.y2}&ms=${command.ms}`);
378
395
  case "key":
379
396
  return this.send(`/hand/key?name=${encodeURIComponent(command.name)}&code=${command.code}`);
380
397
  case "text":
@@ -444,6 +461,10 @@ var describeCommand = (command) => {
444
461
  return `\u70B9 (${command.x},${command.y})`;
445
462
  case "swipe":
446
463
  return `\u4ECE (${command.x1},${command.y1}) \u6ED1\u5230 (${command.x2},${command.y2})`;
464
+ case "pinch":
465
+ return `\u5728 (${command.cx},${command.cy}) ${command.scale >= 1 ? "\u653E\u5927" : "\u7F29\u5C0F"} ${command.scale} \u500D`;
466
+ case "drag":
467
+ return `\u628A (${command.x1},${command.y1}) \u62D6\u5230 (${command.x2},${command.y2})`;
447
468
  case "key":
448
469
  return `\u6309 ${command.name}`;
449
470
  case "text":
@@ -469,11 +490,18 @@ var Hands = class {
469
490
  this.eyes = new EyesClient(() => this.port(this.cfg().eyesPort));
470
491
  this.rootActuator = new RootActuator(() => this.cfg().rootCommand, () => this.log);
471
492
  this.shellActuator = new ShellActuator(() => this.cfg().shellCommand, () => this.log);
472
- this.eyesActuator = new EyesActuator(() => this.port(this.cfg().eyesPort), () => this.readToken(), () => this.log);
493
+ this.eyesActuator = new EyesActuator(() => this.handPort(), () => this.readToken(), () => this.log);
473
494
  }
474
- port(value) {
495
+ port(value, fallback = 17898) {
475
496
  const n = Math.round(Number(value));
476
- return Number.isFinite(n) && n >= 1024 && n <= 65535 ? n : 17898;
497
+ return Number.isFinite(n) && n >= 1024 && n <= 65535 ? n : fallback;
498
+ }
499
+ /** 手势端点在哪:handPort 为 0 时和眼睛同一个口。 */
500
+ handPort() {
501
+ const cfg = this.cfg();
502
+ const explicit = Math.round(Number(cfg.handPort));
503
+ if (Number.isFinite(explicit) && explicit >= 1024 && explicit <= 65535) return explicit;
504
+ return this.port(cfg.eyesPort);
477
505
  }
478
506
  /** 世界挂上以后把日志接进来;探测和动作都写在这里。 */
479
507
  attachLog(log) {
@@ -558,21 +586,18 @@ var Hands = class {
558
586
  if (!picked) throw new HandError(`\u624B\u73B0\u5728\u52A8\u4E0D\u4E86\uFF1A${state.detail}`);
559
587
  return picked;
560
588
  }
561
- /** 屏幕尺寸;拿不到就不校验边界(root 通道下常见)。 */
589
+ /**
590
+ * 屏幕尺寸。只认手势端点报的(眼睛报的是上一次截图的尺寸,常被 max= 缩过,拿来判越界会误拒)。
591
+ * 拿不到就不校验边界。
592
+ */
562
593
  async screenSize() {
563
594
  if (this.screen && Date.now() - this.lastScreenAt < 6e4) return this.screen;
564
- const health = await this.eyes.health();
595
+ const health = await getJson(this.handPort(), "/health", 4e3);
565
596
  if (health?.ok && health.frameWidth && health.frameHeight) {
566
597
  this.screen = { w: health.frameWidth, h: health.frameHeight };
567
598
  this.lastScreenAt = Date.now();
568
599
  return this.screen;
569
600
  }
570
- const frame = await this.eyes.frame(240, 30);
571
- if (frame && frame.width > 0 && frame.height > 0) {
572
- this.screen = { w: frame.width, h: frame.height };
573
- this.lastScreenAt = Date.now();
574
- return this.screen;
575
- }
576
601
  return null;
577
602
  }
578
603
  prune() {
@@ -628,6 +653,39 @@ var Hands = class {
628
653
  const ms = milliseconds(opts.ms, 300);
629
654
  return this.perform({ kind: "swipe", x1: a, y1: b, x2: c, y2: d, ms });
630
655
  }
656
+ async pinch(cx, cy, scale, opts = {}) {
657
+ const x = coord(cx);
658
+ const y = coord(cy);
659
+ const ms = milliseconds(opts.ms, 400);
660
+ const factor = Math.max(0.25, Math.min(4, Number(scale) || 1));
661
+ return this.perform({ kind: "pinch", cx: x, cy: y, scale: factor, ms });
662
+ }
663
+ async drag(x1, y1, x2, y2, opts = {}) {
664
+ const a = coord(x1);
665
+ const b = coord(y1);
666
+ const c = coord(x2);
667
+ const d = coord(y2);
668
+ await this.bounds(a, b);
669
+ await this.bounds(c, d);
670
+ const ms = milliseconds(opts.ms, 900);
671
+ return this.perform({ kind: "drag", x1: a, y1: b, x2: c, y2: d, ms });
672
+ }
673
+ /** 一屏屏翻着找文字(只查不点)。 */
674
+ async findItem(text, opts = {}) {
675
+ const cfg = this.cfg();
676
+ const tries = Math.max(0, Math.min(12, Math.round(Number(opts.tries ?? 5))));
677
+ const scroll = opts.scroll === false ? "0" : "1";
678
+ const token = cfg.handToken ? `&t=${encodeURIComponent(cfg.handToken)}` : "";
679
+ const path = `/hand/find?text=${encodeURIComponent(text)}&tries=${tries}&scroll=${scroll}${token}`;
680
+ const res = await get(this.port(cfg.handPort), path, ACTION_TIMEOUT_MS);
681
+ let body = {};
682
+ try {
683
+ body = JSON.parse(res.body.toString("utf8"));
684
+ } catch {
685
+ }
686
+ if (res.status === 200 && body.ok) return { ok: true, x: body.x, y: body.y, detail: flat(body.detail, 80) };
687
+ return { ok: false, detail: flat(body.error || `\u627E\u4E0D\u5230\u300C${text}\u300D`, 120) };
688
+ }
631
689
  async press(key) {
632
690
  const name = String(key ?? "").trim().toLowerCase().replace(/[\s_-]/g, "");
633
691
  const code = keyCode(name);
@@ -778,6 +836,51 @@ var HANDS_TOOL_DECLS = [
778
836
  required: ["x1", "y1", "x2", "y2"]
779
837
  }
780
838
  },
839
+ {
840
+ name: "hand_pinch",
841
+ tags: ["act"],
842
+ description: "\u53CC\u6307\u7F29\u653E\uFF1A\u653E\u5927\u6216\u7F29\u5C0F\u56FE\u7247\u3001\u5730\u56FE\u3001\u7F51\u9875\u3002scale>1 \u653E\u5927\uFF0C<1 \u7F29\u5C0F\uFF1B\u4E0D\u7ED9 cx/cy \u5C31\u4EE5\u5C4F\u5E55\u4E2D\u5FC3\u4E3A\u57FA\u51C6\u3002",
843
+ parameters: {
844
+ type: "object",
845
+ properties: {
846
+ scale: { type: "number", minimum: 0.25, maximum: 4, description: "\u500D\u6570\uFF1A1.5 \u653E\u5927\u4E00\u534A\uFF0C0.6 \u7F29\u5C0F\u3002" },
847
+ cx: { type: "integer", description: "\u4E2D\u5FC3\u6A2A\u5750\u6807\uFF08\u53EF\u7701\u7565\uFF09\u3002" },
848
+ cy: { type: "integer", description: "\u4E2D\u5FC3\u7EB5\u5750\u6807\uFF08\u53EF\u7701\u7565\uFF09\u3002" },
849
+ ms: { type: "integer", minimum: 120, maximum: 2e3, description: "\u624B\u52BF\u65F6\u957F\uFF0C\u9ED8\u8BA4 400 \u6BEB\u79D2\u3002" }
850
+ },
851
+ required: ["scale"]
852
+ }
853
+ },
854
+ {
855
+ name: "hand_drag",
856
+ tags: ["act"],
857
+ description: "\u957F\u6309\u540E\u62D6\u62FD\uFF1A\u62D6\u5217\u8868\u9879\u3001\u62D6\u6ED1\u5757\u3001\u62D6\u56FE\u6807\u3001\u62D6\u8FDB\u5EA6\u3002\u548C swipe \u7684\u533A\u522B\u662F\u5B83\u6309\u4F4F\u4E0D\u653E\u518D\u62D6\uFF0C\u9002\u5408\u6709\u62D6\u62FD\u8BED\u4E49\u7684\u63A7\u4EF6\u3002",
858
+ parameters: {
859
+ type: "object",
860
+ properties: {
861
+ x1: { type: "integer", description: "\u8D77\u70B9\u6A2A\u5750\u6807\u3002" },
862
+ y1: { type: "integer", description: "\u8D77\u70B9\u7EB5\u5750\u6807\u3002" },
863
+ x2: { type: "integer", description: "\u7EC8\u70B9\u6A2A\u5750\u6807\u3002" },
864
+ y2: { type: "integer", description: "\u7EC8\u70B9\u7EB5\u5750\u6807\u3002" },
865
+ ms: { type: "integer", minimum: 300, maximum: 4e3, description: "\u62D6\u591A\u4E45\uFF0C\u9ED8\u8BA4 900 \u6BEB\u79D2\u3002" }
866
+ },
867
+ required: ["x1", "y1", "x2", "y2"]
868
+ }
869
+ },
870
+ {
871
+ name: "hand_find",
872
+ tags: ["read"],
873
+ description: "\u4E00\u5C4F\u5C4F\u5F80\u4E0B\u7FFB\uFF0C\u627E\u5C4F\u5E55\u4E0A\u542B\u6709\u67D0\u6BB5\u6587\u5B57\u7684\u4E1C\u897F\uFF0C\u8FD4\u56DE\u5B83\u7684\u5750\u6807\uFF08\u53EA\u627E\u4E0D\u70B9\uFF09\u3002\u8868\u683C\u3001\u957F\u5217\u8868\u3001\u8BBE\u7F6E\u9879\u91CC\u627E\u4E1C\u897F\u7528\u5B83\uFF0C\u6BD4\u8089\u773C\u4E00\u9875\u9875\u770B\u5FEB\u3002",
874
+ parameters: {
875
+ type: "object",
876
+ properties: {
877
+ text: { type: "string", description: "\u8981\u627E\u7684\u6587\u5B57\uFF08\u5305\u542B\u5339\u914D\uFF0C\u4E0D\u533A\u5206\u5927\u5C0F\u5199\uFF09\u3002" },
878
+ tries: { type: "integer", minimum: 0, maximum: 12, description: "\u6700\u591A\u7FFB\u51E0\u5C4F\uFF0C\u9ED8\u8BA4 5\u3002" },
879
+ scroll: { type: "boolean", description: "\u627E\u4E0D\u5230\u65F6\u662F\u5426\u81EA\u52A8\u6EDA\u52A8\uFF0C\u9ED8\u8BA4 true\u3002" }
880
+ },
881
+ required: ["text"]
882
+ }
883
+ },
781
884
  {
782
885
  name: "hand_type",
783
886
  tags: ["act"],
@@ -886,6 +989,12 @@ var HandsWorld = class {
886
989
  return await this.long(args);
887
990
  case "hand_swipe":
888
991
  return await this.swipe(args);
992
+ case "hand_pinch":
993
+ return await this.pinch(args);
994
+ case "hand_drag":
995
+ return await this.drag(args);
996
+ case "hand_find":
997
+ return await this.findItem(args);
889
998
  case "hand_type":
890
999
  return await this.type(args);
891
1000
  case "hand_key":
@@ -922,6 +1031,44 @@ var HandsWorld = class {
922
1031
  return { text: report.text, ...blobs.length ? { blobs } : {} };
923
1032
  }
924
1033
  /** 给文字就先在屏幕上找,找到唯一一处才点;给坐标就直接点。 */
1034
+ async pinch(args) {
1035
+ const scale = Number(args.scale);
1036
+ if (!Number.isFinite(scale) || scale <= 0) return failed("scale \u8981\u662F\u6B63\u6570\uFF0C\u6BD4\u5982 1.5 \u653E\u5927\u30010.6 \u7F29\u5C0F\u3002");
1037
+ const cx = args.cx === void 0 ? void 0 : Number(args.cx);
1038
+ const cy = args.cy === void 0 ? void 0 : Number(args.cy);
1039
+ const ms = args.ms === void 0 ? void 0 : Number(args.ms);
1040
+ const size = await this.hands.screenSize();
1041
+ const x = cx ?? Math.round((size?.w ?? 1080) / 2);
1042
+ const y = cy ?? Math.round((size?.h ?? 1920) / 2);
1043
+ const receipt = await this.hands.pinch(x, y, scale, ms === void 0 ? {} : { ms });
1044
+ this.note(receipt);
1045
+ return receipt.text;
1046
+ }
1047
+ async drag(args) {
1048
+ const need = ["x1", "y1", "x2", "y2"];
1049
+ if (need.some((k) => args[k] === void 0)) return failed("\u62D6\u62FD\u8981\u7ED9\u9F50 x1\u3001y1\u3001x2\u3001y2\u3002");
1050
+ const receipt = await this.hands.drag(
1051
+ Number(args.x1),
1052
+ Number(args.y1),
1053
+ Number(args.x2),
1054
+ Number(args.y2),
1055
+ args.ms === void 0 ? {} : { ms: Number(args.ms) }
1056
+ );
1057
+ this.note(receipt);
1058
+ return receipt.text;
1059
+ }
1060
+ async findItem(args) {
1061
+ const text = typeof args.text === "string" ? args.text.trim() : "";
1062
+ if (!text) return failed("\u8981\u627E\u4EC0\u4E48\u6587\u5B57\uFF1F\u7ED9 text\u3002");
1063
+ const tries = args.tries === void 0 ? void 0 : Number(args.tries);
1064
+ const scroll = args.scroll === void 0 ? void 0 : Boolean(args.scroll);
1065
+ const found = await this.hands.findItem(text, {
1066
+ ...tries === void 0 ? {} : { tries },
1067
+ ...scroll === void 0 ? {} : { scroll }
1068
+ });
1069
+ if (!found.ok) return failed(found.detail);
1070
+ return `\u627E\u5230\u4E86\u300C${text}\u300D@(${found.x},${found.y})${found.detail ? `\uFF08${found.detail}\uFF09` : ""}\uFF1B\u8981\u52A8\u624B\u5C31 hand_tap \u4F20\u8FD9\u7EC4\u5750\u6807\u3002`;
1071
+ }
925
1072
  async tap(args) {
926
1073
  const text = typeof args.text === "string" ? args.text.trim() : "";
927
1074
  if (text) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
- "description": "Cortico World: hands for the desktop pet — looks at the phone screen, then taps, swipes, types and presses keys through whatever actuator the device actually has",
5
+ "description": "Cortico World: hands for the desktop pet — looks at the phone screen, then taps, swipes, pinches, drags, types and presses keys through whatever actuator the device actually has",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "cortico-world",
@@ -28,4 +28,4 @@
28
28
  "README.md"
29
29
  ],
30
30
  "scripts": {}
31
- }
31
+ }