@swmansion/argent 0.15.1-next.4 → 0.15.1-next.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.
@@ -126102,7 +126102,10 @@ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
126102
126102
  var zodSchema16 = external_exports.object({
126103
126103
  udid: external_exports.string().describe("Target device id from `list-devices` (iOS UDID, Android serial, or Chromium id)."),
126104
126104
  x: external_exports.number().describe("Normalized horizontal position 0.0\u20131.0 (left=0, right=1), not pixels"),
126105
- y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels")
126105
+ y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels"),
126106
+ clickCount: external_exports.number().int().min(1).max(10).optional().describe(
126107
+ "Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires. Default 1."
126108
+ )
126106
126109
  });
126107
126110
  var capability8 = {
126108
126111
  apple: { simulator: true, device: true },
@@ -126110,19 +126113,25 @@ var capability8 = {
126110
126113
  android: { emulator: true, device: true, unknown: true },
126111
126114
  chromium: { app: true }
126112
126115
  };
126113
- async function tapChromium(api, x, y) {
126116
+ var TAP_HOLD_MS = 50;
126117
+ var MULTI_TAP_GAP_MS = 100;
126118
+ async function tapChromium(api, x, y, clickCount) {
126114
126119
  const vp = api.getViewport();
126115
126120
  const pxX = Math.max(0, Math.min(vp.width, x * vp.width));
126116
126121
  const pxY = Math.max(0, Math.min(vp.height, y * vp.height));
126117
126122
  await api.dispatchMouseEvent({ type: "mouseMoved", x: pxX, y: pxY });
126118
- await api.dispatchMouseEvent({ type: "mousePressed", x: pxX, y: pxY, clickCount: 1 });
126119
- await sleep2(50);
126120
- await api.dispatchMouseEvent({ type: "mouseReleased", x: pxX, y: pxY, clickCount: 1 });
126123
+ for (let i = 1; i <= clickCount; i++) {
126124
+ if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
126125
+ await api.dispatchMouseEvent({ type: "mousePressed", x: pxX, y: pxY, clickCount: i });
126126
+ await sleep2(TAP_HOLD_MS);
126127
+ await api.dispatchMouseEvent({ type: "mouseReleased", x: pxX, y: pxY, clickCount: i });
126128
+ }
126121
126129
  }
126122
126130
  var gestureTapTool = {
126123
126131
  id: "gesture-tap",
126124
126132
  description: `Press the device screen (iOS simulator, Android emulator, or Chromium app) at normalized coordinates: x and y are fractions of screen width and height in 0.0\u20131.0 (not pixels).
126125
126133
  Sends a Down event followed by an Up event at the same point. For Chromium, this dispatches a CDP mouse-press/release on the renderer.
126134
+ Set clickCount: 2 for a double-tap / double-click \u2014 the taps are dispatched as one gesture with proper click counting, which two separate tap calls cannot guarantee.
126126
126135
  Use when you need to tap a button, link, or any tappable element on the screen.
126127
126136
  Returns { tapped: true, timestampMs }. Fails if the simulator-server / emulator backend / Chromium CDP is not reachable for the given device.
126128
126137
  Before tapping, determine the correct coordinates by using discovery tools \u2014 pick by platform: iOS / Android use \`describe\`, \`native-describe-screen\`, or \`debugger-component-tree\`; Chromium uses \`describe\` (the DOM walker), since the native and RN-specific discovery tools don't apply. More information in \`argent-device-interact\` skill`,
@@ -126140,29 +126149,33 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
126140
126149
  async execute(services, params) {
126141
126150
  const device = resolveDevice(params.udid);
126142
126151
  const timestampMs = Date.now();
126152
+ const clickCount = params.clickCount ?? 1;
126143
126153
  if (device.platform === "chromium") {
126144
126154
  const chromium = services.chromium;
126145
- await tapChromium(chromium, params.x, params.y);
126155
+ await tapChromium(chromium, params.x, params.y, clickCount);
126146
126156
  return { tapped: true, timestampMs };
126147
126157
  }
126148
126158
  const api = services.simulatorServer;
126149
- sendCommand(api, {
126150
- cmd: "touch",
126151
- type: "Down",
126152
- x: params.x,
126153
- y: params.y,
126154
- second_x: null,
126155
- second_y: null
126156
- });
126157
- await sleep2(50);
126158
- sendCommand(api, {
126159
- cmd: "touch",
126160
- type: "Up",
126161
- x: params.x,
126162
- y: params.y,
126163
- second_x: null,
126164
- second_y: null
126165
- });
126159
+ for (let i = 1; i <= clickCount; i++) {
126160
+ if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
126161
+ sendCommand(api, {
126162
+ cmd: "touch",
126163
+ type: "Down",
126164
+ x: params.x,
126165
+ y: params.y,
126166
+ second_x: null,
126167
+ second_y: null
126168
+ });
126169
+ await sleep2(TAP_HOLD_MS);
126170
+ sendCommand(api, {
126171
+ cmd: "touch",
126172
+ type: "Up",
126173
+ x: params.x,
126174
+ y: params.y,
126175
+ second_x: null,
126176
+ second_y: null
126177
+ });
126178
+ }
126166
126179
  return { tapped: true, timestampMs };
126167
126180
  }
126168
126181
  };
@@ -133567,7 +133580,7 @@ a prior tap), use individual tool calls instead.
133567
133580
 
133568
133581
  Allowed tools and their args (udid is auto-injected, do NOT include it in args):
133569
133582
 
133570
- gesture-tap: { x: number, y: number } [ios/android/chromium]
133583
+ gesture-tap: { x: number, y: number, clickCount?: number } [ios/android/chromium]
133571
133584
  gesture-swipe: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [ios/android]
133572
133585
  gesture-scroll: { x: number, y: number, deltaX?: number, deltaY?: number, durationMs?: number } [chromium only]
133573
133586
  gesture-drag: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [chromium only]
@@ -144544,7 +144557,12 @@ function toYamlStep(step) {
144544
144557
  case "run":
144545
144558
  return { run: step.flow };
144546
144559
  case "tap": {
144547
- const body = step.selector ? selectorToYaml(step.selector) : { x: step.x, y: step.y };
144560
+ if (step.selector) {
144561
+ const sel = selectorToYaml(step.selector);
144562
+ return { tap: step.times !== void 0 ? { on: sel, times: step.times } : sel };
144563
+ }
144564
+ const body = { x: step.x, y: step.y };
144565
+ if (step.times !== void 0) body.times = step.times;
144548
144566
  return { tap: body };
144549
144567
  }
144550
144568
  case "long-press": {
@@ -144839,6 +144857,50 @@ function parseLongPress(body, entry) {
144839
144857
  }
144840
144858
  return { kind: "long-press", selector: parseSelector(body, "long-press") };
144841
144859
  }
144860
+ function parseTapTimes(raw, entry) {
144861
+ if (raw === void 0) return void 0;
144862
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 10) {
144863
+ badEntry(entry, "tap.times must be an integer between 1 and 10 (2 = double-tap)");
144864
+ }
144865
+ return raw === 1 ? void 0 : raw;
144866
+ }
144867
+ function parseTap(body, entry) {
144868
+ const obj = body !== null && typeof body === "object" ? body : {};
144869
+ if (obj.x !== void 0 || obj.y !== void 0) {
144870
+ if (obj.on !== void 0 || obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
144871
+ badEntry(entry, "tap takes a selector or x/y coordinates, not both");
144872
+ }
144873
+ if (typeof obj.x !== "number" || typeof obj.y !== "number") {
144874
+ badEntry(entry, "a coordinate tap needs numeric x and y");
144875
+ }
144876
+ if (!Object.keys(obj).every((k) => k === "x" || k === "y" || k === "times")) {
144877
+ badEntry(entry, "a coordinate tap takes only { x, y, times }");
144878
+ }
144879
+ const step = { kind: "tap", x: obj.x, y: obj.y };
144880
+ const times = parseTapTimes(obj.times, entry);
144881
+ if (times !== void 0) step.times = times;
144882
+ return step;
144883
+ }
144884
+ if (obj.on !== void 0 || obj.times !== void 0) {
144885
+ if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
144886
+ badEntry(
144887
+ entry,
144888
+ 'the tap options form takes a nested selector \u2014 e.g. tap: { on: { text: "Photo" }, times: 2 }'
144889
+ );
144890
+ }
144891
+ if (!Object.keys(obj).every((k) => k === "on" || k === "times")) {
144892
+ badEntry(entry, "the tap options form accepts only { on, times }");
144893
+ }
144894
+ if (obj.on === void 0) {
144895
+ badEntry(entry, 'tap with times needs a target \u2014 e.g. tap: { on: "Photo", times: 2 }');
144896
+ }
144897
+ const step = { kind: "tap", selector: parseSelector(obj.on, "tap.on") };
144898
+ const times = parseTapTimes(obj.times, entry);
144899
+ if (times !== void 0) step.times = times;
144900
+ return step;
144901
+ }
144902
+ return { kind: "tap", selector: parseSelector(body, "tap") };
144903
+ }
144842
144904
  function fromYamlStep(raw) {
144843
144905
  const entry = raw;
144844
144906
  const kinds = STEP_DIRECTIVE_KEYS.filter((k) => k in entry);
@@ -144864,23 +144926,7 @@ function fromYamlStep(raw) {
144864
144926
  if ("echo" in raw) return { kind: "echo", message: String(raw.echo) };
144865
144927
  if ("launch" in raw) return { kind: "launch", app: parseLaunch(raw.launch) };
144866
144928
  if ("run" in raw) return { kind: "run", flow: String(raw.run) };
144867
- if ("tap" in raw) {
144868
- const body = raw.tap;
144869
- const obj = body !== null && typeof body === "object" ? body : {};
144870
- if (body !== null && typeof body === "object" && !Array.isArray(body)) {
144871
- rejectUnknownKeys(raw, obj, [...SELECTOR_KEYS, "x", "y"], "tap");
144872
- }
144873
- if (obj.x !== void 0 || obj.y !== void 0) {
144874
- if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
144875
- badEntry(raw, "tap takes a selector or x/y coordinates, not both");
144876
- }
144877
- if (typeof obj.x !== "number" || typeof obj.y !== "number") {
144878
- badEntry(raw, "a coordinate tap needs numeric x and y");
144879
- }
144880
- return { kind: "tap", x: obj.x, y: obj.y };
144881
- }
144882
- return { kind: "tap", selector: parseSelector(body, "tap") };
144883
- }
144929
+ if ("tap" in raw) return parseTap(raw.tap, raw);
144884
144930
  if ("long-press" in raw) {
144885
144931
  return parseLongPress(raw["long-press"], raw);
144886
144932
  }
@@ -145719,13 +145765,15 @@ If a step was recorded by mistake, edit the .yaml file directly to remove it.`,
145719
145765
  const runTarget = params.command === RUN_TARGET_COMMAND && params.delayMs === void 0 ? await captureRunTarget(session, args) : void 0;
145720
145766
  const strippedArgs = stripDeviceKeys(args);
145721
145767
  const isLaunch = params.command === "restart-app" && params.delayMs === void 0 && typeof strippedArgs.bundleId === "string" && Object.keys(strippedArgs).length === 1;
145768
+ const cc = args.clickCount;
145769
+ const tapTimes = isTap && typeof cc === "number" && Number.isInteger(cc) && cc >= 2 && cc <= 10 ? { times: cc } : {};
145722
145770
  let step;
145723
145771
  let warning;
145724
145772
  if (captured?.selector) {
145725
- step = { kind: "tap", selector: captured.selector };
145773
+ step = { kind: "tap", selector: captured.selector, ...tapTimes };
145726
145774
  warning = captured.warning;
145727
145775
  } else if (isTap) {
145728
- step = { kind: "tap", x: args.x, y: args.y };
145776
+ step = { kind: "tap", x: args.x, y: args.y, ...tapTimes };
145729
145777
  warning = captured?.warning;
145730
145778
  } else if (isLaunch) {
145731
145779
  step = { kind: "launch", app: strippedArgs.bundleId };
@@ -146101,7 +146149,10 @@ async function runTap(env, target) {
146101
146149
  } else {
146102
146150
  return { ok: false, reason: "tap needs a selector or x/y coordinates" };
146103
146151
  }
146104
- await invokeOnDevice(env, "gesture-tap", point);
146152
+ await invokeOnDevice(env, "gesture-tap", {
146153
+ ...point,
146154
+ ...target.times !== void 0 ? { clickCount: target.times } : {}
146155
+ });
146105
146156
  return { ok: true };
146106
146157
  }
146107
146158
  var DEFAULT_LONG_PRESS_MS = 800;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.15.1-next.4",
3
+ "version": "0.15.1-next.5",
4
4
  "description": "MCP server for iOS Simulator and Android Emulator control",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -23,7 +23,7 @@ Beyond raw `tool:` steps and `echo:`, flows support declarative directives inter
23
23
  | Directive | YAML | Meaning |
24
24
  | ------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
25
25
  | `launch` | `- launch: com.acme.app` or `- launch: { ios: …, android: … }` | start the app from scratch (terminate + relaunch) and wait until ready |
26
- | `tap` | `- tap: Login` or `- tap: { x: 0.5, y: 0.57 }` | tap an element by selector (auto-waits), or a raw normalized point |
26
+ | `tap` | `- tap: Login`, `- tap: { x: 0.5, y: 0.57 }`, `- tap: { on: Login, times: 2 }` | tap by selector (auto-waits) or raw point; `times: 2` = double-tap |
27
27
  | `long-press` | `- long-press: Row 3` or `- long-press: { on: <sel>, duration: 1200 }` | press and hold an element (default 800ms; Chromium: mouse press-hold) |
28
28
  | `type` | `- type: { into: email, text: "a@b.com" }` | focus a field, type, then press Enter to submit + dismiss the keyboard |
29
29
  | `scroll-to` | `- scroll-to: "Order #1234"` (scrolls down) or `- scroll-to: { target: …, direction: right, within: … }` | momentum-free scroll until the target is visible |