cortico-world-hands 0.20.3 → 0.20.5

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 +126 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -719,6 +719,12 @@ var MIN_GESTURE_MS = 20;
719
719
  var MAX_GESTURE_MS = 2e4;
720
720
  var HandError = class extends Error {
721
721
  };
722
+ function parseRect(raw) {
723
+ if (!raw) return void 0;
724
+ const m = raw.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
725
+ if (!m) return void 0;
726
+ return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
727
+ }
722
728
  function get(port, path, timeoutMs) {
723
729
  return new Promise((resolve2, reject) => {
724
730
  const req = httpGet({ host: "127.0.0.1", port, path, timeout: timeoutMs }, (res) => {
@@ -1016,6 +1022,20 @@ var EyesClient = class {
1016
1022
  }
1017
1023
  return { ok: body.ok !== false, pkg: flat(body.pkg, 120), items };
1018
1024
  }
1025
+ /** 只截一块并放大:x,y,w,h 是真实像素。 */
1026
+ async zoom(x, y, w, h, maxWidth = 1080, quality = 80) {
1027
+ const cfg = this.cfg();
1028
+ const path = `/screen.jpg?x=${Math.max(0, Math.round(x))}&y=${Math.max(0, Math.round(y))}&w=${Math.max(8, Math.round(w))}&h=${Math.max(8, Math.round(h))}&max=${Math.round(maxWidth)}&q=${Math.round(quality)}`;
1029
+ const res = await get(this.handPort(), path, 12e3);
1030
+ if (res.status !== 200 || res.body.length === 0) return null;
1031
+ return {
1032
+ bytes: new Uint8Array(res.body),
1033
+ width: Number.parseInt(res.header("x-width"), 10) || 0,
1034
+ height: Number.parseInt(res.header("x-height"), 10) || 0,
1035
+ pkg: "",
1036
+ ageMs: 0
1037
+ };
1038
+ }
1019
1039
  async frame(maxWidth, quality) {
1020
1040
  try {
1021
1041
  const w = Math.max(160, Math.min(2160, Math.round(maxWidth)));
@@ -1051,6 +1071,10 @@ var describeCommand = (command) => {
1051
1071
  }
1052
1072
  };
1053
1073
  var Hands = class {
1074
+ /** 读屏幕上的字(转发给眼睛客户端)。 */
1075
+ async uiSnapshot() {
1076
+ return this.eyes.ui();
1077
+ }
1054
1078
  eyes;
1055
1079
  rootActuator;
1056
1080
  shellActuator;
@@ -1096,7 +1120,7 @@ var Hands = class {
1096
1120
  for (const item of ui.items) {
1097
1121
  if (!item.center) continue;
1098
1122
  if (!item.text.toLowerCase().includes(query)) continue;
1099
- hits2.push({ text: item.text, center: item.center });
1123
+ hits2.push({ text: item.text, center: item.center, rect: parseRect(item.bounds) });
1100
1124
  if (hits2.length >= 20) break;
1101
1125
  }
1102
1126
  return hits2;
@@ -2122,6 +2146,45 @@ var HANDS_TOOL_DECLS = [
2122
2146
  },
2123
2147
  required: ["content"]
2124
2148
  }
2149
+ },
2150
+ {
2151
+ name: "hand_zoom",
2152
+ tags: ["read"],
2153
+ description: "\u53EA\u622A\u5C4F\u5E55\u7684\u4E00\u5C0F\u5757\u5E76\u653E\u5927\u2014\u2014\u5C0F\u5B57\u3001\u5C0F\u6309\u94AE\u770B\u4E0D\u6E05\u65F6\u7528\u5B83\u3002\u5148 hand_see \u62FF\u5230\u5750\u6807\uFF0C\u518D\u628A\u8FD9\u5757\u7684 x/y/\u5BBD/\u9AD8 \u4EA4\u7ED9\u5B83\u3002",
2154
+ parameters: {
2155
+ type: "object",
2156
+ properties: {
2157
+ x: { type: "integer", description: "\u5DE6\u4E0A\u89D2\u6A2A\u5750\u6807\uFF08\u771F\u5B9E\u50CF\u7D20\uFF09" },
2158
+ y: { type: "integer", description: "\u5DE6\u4E0A\u89D2\u7EB5\u5750\u6807" },
2159
+ w: { type: "integer", description: "\u5BBD" },
2160
+ h: { type: "integer", description: "\u9AD8" }
2161
+ },
2162
+ required: ["x", "y", "w", "h"]
2163
+ }
2164
+ },
2165
+ {
2166
+ name: "hand_read_page",
2167
+ tags: ["read"],
2168
+ description: "\u628A\u4E00\u6574\u9875\u8BFB\u5B8C\uFF1A\u5F53\u524D\u5C4F\u7684\u6587\u5B57 + \u5F80\u4E0A\u6EDA\u51E0\u6B21\u628A\u540E\u9762\u7684\u4E5F\u8BFB\u8FDB\u6765\uFF0C\u53BB\u91CD\u540E\u62FC\u6210\u4E00\u6BB5\u3002\u957F\u5217\u8868\u3001\u957F\u6587\u7AE0\u7528\u5B83\uFF0C\u522B\u4E00\u5C4F\u4E00\u5C4F\u95EE\u3002",
2169
+ parameters: {
2170
+ type: "object",
2171
+ properties: {
2172
+ pages: { type: "integer", description: "\u6700\u591A\u7FFB\u51E0\u5C4F\uFF0C\u9ED8\u8BA4 3\uFF0C\u6700\u591A 8" }
2173
+ }
2174
+ }
2175
+ },
2176
+ {
2177
+ name: "hand_read_text",
2178
+ tags: ["read"],
2179
+ description: "\u8BFB\u67D0\u4E00\u5904\u6587\u5B57\uFF1B\u5B57\u592A\u5C0F\uFF08\u9AD8\u5EA6\u4E0D\u8DB3 40 \u50CF\u7D20\uFF09\u4F1A\u81EA\u52A8\u628A\u90A3\u4E00\u5757\u653E\u5927\u518D\u7ED9\u4F60\u770B\uFF0C\u7701\u5F97\u4F60\u81EA\u5DF1\u8C03 hand_zoom\u3002",
2180
+ parameters: {
2181
+ type: "object",
2182
+ properties: {
2183
+ text: { type: "string", description: "\u8981\u8BFB\u7684\u90A3\u5904\u6587\u5B57\uFF08\u53EF\u4EE5\u53EA\u7ED9\u4E00\u90E8\u5206\uFF09" },
2184
+ nth: { type: "integer", description: "\u540C\u540D\u591A\u5904\u65F6\u53D6\u7B2C\u51E0\u4E2A\uFF0C\u4ECE 0 \u5F00\u59CB" }
2185
+ },
2186
+ required: ["text"]
2187
+ }
2125
2188
  }
2126
2189
  ];
2127
2190
 
@@ -2530,6 +2593,67 @@ ${lines.join("\n")}
2530
2593
  return await this.runSync(true);
2531
2594
  case "sync_status":
2532
2595
  return this.lastSync ? `\u4E0A\u6B21\u540C\u6B65 ${this.lastSync.at.slice(0, 19)}\uFF0C\u8BB0\u5FC6 ${this.lastSync.lines} \u6761\uFF0C\u4EBA\u683C ${this.lastSync.persona} \u5B57` : "\u8FD8\u6CA1\u540C\u6B65\u8FC7";
2596
+ case "hand_read_page": {
2597
+ const pages = Math.max(1, Math.min(8, Math.round(Number(args.pages) || 3)));
2598
+ const seen = /* @__PURE__ */ new Set();
2599
+ const out = [];
2600
+ for (let i = 0; i < pages; i++) {
2601
+ const ui = await this.hands.uiSnapshot();
2602
+ if (!ui.ok) break;
2603
+ for (const item of ui.items) {
2604
+ const t = (item.text || "").trim();
2605
+ if (!t || t.length < 2 || seen.has(t)) continue;
2606
+ seen.add(t);
2607
+ out.push(t);
2608
+ }
2609
+ if (i < pages - 1) {
2610
+ await this.hands.swipe(1550, 1500, 1550, 700, 420);
2611
+ await new Promise((r) => setTimeout(r, 550));
2612
+ }
2613
+ }
2614
+ if (out.length === 0) return failed("\u8FD9\u4E00\u9875\u6CA1\u8BFB\u5230\u80FD\u5FF5\u7684\u5B57");
2615
+ const text = out.join("\uFF5C");
2616
+ return `\u8FD9\u4E00\u9875\u8BFB\u4E86 ${out.length} \u6BB5
2617
+ ${text.slice(0, 2200)}`;
2618
+ }
2619
+ case "hand_read_text": {
2620
+ const needle = typeof args.text === "string" ? args.text.trim() : "";
2621
+ if (!needle) return failed("\u8981\u8BFB\u54EA\u4E00\u6BB5\uFF1F\u7ED9 text");
2622
+ const hits2 = await this.hands.findText(needle);
2623
+ if (hits2.length === 0) return failed(`\u5C4F\u5E55\u4E0A\u6CA1\u627E\u5230\u300C${needle}\u300D`);
2624
+ const nth = args.nth === void 0 ? 0 : Math.max(0, Math.round(Number(args.nth)));
2625
+ const hit = hits2[nth];
2626
+ if (!hit) return failed(`\u300C${needle}\u300D\u53EA\u6709 ${hits2.length} \u5904`);
2627
+ const b = hit.rect;
2628
+ const h = b ? b[3] - b[1] : 0;
2629
+ if (h > 0 && h < 40) {
2630
+ const pad = 12;
2631
+ const f = await this.hands.zoom(
2632
+ Math.max(0, b[0] - pad),
2633
+ Math.max(0, b[1] - pad),
2634
+ Math.max(16, b[2] - b[0] + pad * 2),
2635
+ Math.max(16, b[3] - b[1] + pad * 2)
2636
+ );
2637
+ if (f) {
2638
+ return {
2639
+ text: `\u300C${hit.text}\u300D\u5B57\u592A\u5C0F\uFF08\u9AD8 ${h}px\uFF09\uFF0C\u6211\u628A\u8FD9\u5757\u653E\u5927\u4E86`,
2640
+ blobs: [{ bytes: f.bytes, mime: "image/jpeg", name: "zoom.jpg", fallbackText: "\uFF08\u8FD9\u4E00\u5757\u7684\u653E\u5927\u56FE\uFF09" }]
2641
+ };
2642
+ }
2643
+ }
2644
+ return `\u300C${hit.text}\u300D@(${hit.center.x},${hit.center.y})`;
2645
+ }
2646
+ case "hand_zoom": {
2647
+ const n = (v) => Math.round(Number(v));
2648
+ const x = n(args.x), y = n(args.y), w = n(args.w), h = n(args.h);
2649
+ if (![x, y, w, h].every(Number.isFinite) || w <= 0 || h <= 0) return failed("\u8981\u7ED9 x/y/w/h\uFF08\u771F\u5B9E\u50CF\u7D20\uFF09");
2650
+ const f = await this.hands.zoom(x, y, w, h);
2651
+ if (!f) return failed("\u8FD9\u4E00\u5757\u6CA1\u622A\u5230\uFF08\u5750\u6807\u53EF\u80FD\u8D85\u51FA\u5C4F\u5E55\uFF09");
2652
+ return {
2653
+ text: `\u653E\u5927\u770B @(${x},${y}) ${w}\xD7${h}`,
2654
+ blobs: [{ bytes: f.bytes, mime: "image/jpeg", name: "zoom.jpg", fallbackText: "\uFF08\u8FD9\u662F\u4ED6\u6307\u5B9A\u90A3\u4E00\u5757\u653E\u5927\u540E\u7684\u622A\u56FE\uFF09" }]
2655
+ };
2656
+ }
2533
2657
  case "hand_see":
2534
2658
  return await this.see();
2535
2659
  case "hand_look":
@@ -2689,7 +2813,7 @@ ${lines.join("\n")}
2689
2813
  async verifyChange(beforePkg) {
2690
2814
  try {
2691
2815
  await new Promise((r) => setTimeout(r, 450));
2692
- const ui = await this.hands.ui();
2816
+ const ui = await this.hands.uiSnapshot();
2693
2817
  const now = ui.ok ? ui.pkg : "";
2694
2818
  if (beforePkg && now && beforePkg !== now) return `\uFF0C\u754C\u9762\u5DF2\u7ECF\u5207\u5230 ${now}`;
2695
2819
  return "\uFF08\u5C4F\u5E55\u770B\u8D77\u6765\u6CA1\u53D8\uFF1B\u5982\u679C\u8BE5\u6709\u53CD\u5E94\uFF0C\u5148 hand_see \u770B\u4E00\u773C\u518D\u70B9\uFF09";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.20.3",
3
+ "version": "0.20.5",
4
4
  "type": "module",
5
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",