@swmansion/argent 0.18.1-next.21 → 0.18.1-next.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.18.1-next.21",
3
+ "version": "0.18.1-next.22",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",
@@ -5,7 +5,7 @@ description: Record a reusable flow (scripted sequence of MCP tool calls) that c
5
5
 
6
6
  ## Overview
7
7
 
8
- A flow is a sequence of steps saved to a `.yaml` file in the `.argent/flows/` directory. Each recorded step is **executed live** as you add it, so you verify it works before it becomes part of the flow. Replay a finished flow with `flow-execute`, or — for an e2e flow — headlessly with `argent flow run <name>`.
8
+ A flow is a sequence of steps saved to a `.yaml` file in the `.argent/flows/` directory. Each recorded step is **executed live** as you add it, so you verify it works before it becomes part of the flow. Replay a finished flow with `flow-execute`, or — for an e2e flow — headlessly with `argent flow run checkout` (a saved flow's name) or `argent flow run path/to/checkout.yaml`.
9
9
 
10
10
  Flows store **no device id**: the runner binds a device (the single booted one, or pass `device`/`platform`). A recorded coordinate `gesture-tap` is captured as a portable `tap: { selector }` step whenever the tapped element has stable text/identifier.
11
11
 
@@ -14,27 +14,27 @@ Flows store **no device id**: the runner binds a device (the single booted one,
14
14
  - **e2e** — begins with a `launch:` step, which starts that app from scratch (terminate + relaunch), so the flow controls its own start state. No `executionPrerequisite`. May `run:` other flows, and (on iOS/Android) may itself be a `run:` target — when nested, its `launch` runs inline, restarting the app for that sub-scenario. **Chromium is the exception:** the runner boots one Electron app per run (the top-level flow's), so a nested chromium e2e flow's `launch` can't boot its own instance and fails the run — keep chromium e2e flows top-level. Record one by adding a `restart-app` of the app under test as the **first** step — it is captured as the `launch` step.
15
15
  - **fragment** — doesn't begin with a launch; runs against the device's current state. May declare an `executionPrerequisite` (a documented entry-state contract). Invoked from other flows via a `run:` step, or directly by you at any time.
16
16
 
17
- Both run via `argent flow run <name>` — a fragment simply runs against whatever is on screen (its prerequisite is printed as a reminder). Only e2e flows are meaningful CI/suite entries, since only they give a deterministic verdict from a clean start.
17
+ Both run via `argent flow run <flow|flow.yaml>` — a fragment simply runs against whatever is on screen (its prerequisite is printed as a reminder). A bare name is read from `.argent/flows/<name>.yaml`; anything ending in `.yaml` is a path. Only e2e flows are meaningful CI/suite entries, since only they give a deterministic verdict from a clean start.
18
18
 
19
19
  ### Step directives
20
20
 
21
21
  Beyond raw `tool:` steps and `echo:`, flows support declarative directives interpreted by the runner (they are **not** agent-callable tools). **Every directive hard-stops the flow on failure**; later steps are reported `skip`.
22
22
 
23
- | Directive | YAML | Meaning |
24
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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`, `- tap: { x: 0.5, y: 0.57 }`, `- tap: { on: Login, times: 2 }`, `- tap: { on: { x: 0.5, y: 0.57 }, times: 2 }` | tap by selector (auto-waits) or raw point; `times` (2 = double-tap) needs the target nested under `on:` — a selector or a point (`{ x, y, times }` is rejected) |
27
- | `long-press` | `- long-press: Row 3`, `- long-press: { x: 0.5, y: 0.6 }`, `- long-press: { on: <sel>, duration: 1200 }`, `- long-press: { on: { x: 0.5, y: 0.6 }, duration: 1200 }` | press and hold an element or raw point (default 800ms; Chromium: mouse press-hold); `duration` needs the target nested under `on:` — a selector or a point |
28
- | `type` | `- type: { into: email, text: "a@b.com" }` | focus a field, type, then press Enter to submit + dismiss the keyboard |
29
- | `scroll-to` | `- scroll-to: "Order #1234"` (scrolls down) or `- scroll-to: { target: …, direction: right, within: … }` | momentum-free scroll until the target is visible |
30
- | `pinch` | `- pinch: { on: "Map", scale: 3 }` or `- pinch: { scale: 0.5 }` | two-finger zoom in (`scale` > 1) or out (`< 1`); big scales chain gestures; `on` optional — defaults to screen center; open-loop — assert the visible result |
31
- | `rotate` | `- rotate: { on: "Map", by: 90 }` or `- rotate: { by: -45 }` | two-finger rotation by degrees (+ CW, − CCW, within ±3000°; options map only); `on` optional — screen center default; not `tool: rotate` (orientation) |
32
- | `await` | `- await: { visible: Home }` | wait for a UI condition |
33
- | `wait` | `- wait: 500` | pause for a fixed number of milliseconds (last resort — prefer `await`) |
34
- | `assert` | `- assert: { visible: Welcome }` | check a condition, hard-fail if it never holds |
35
- | `snapshot` | `- snapshot: home` or `- snapshot: { name: home, maxMismatch: 0.5, cropOn: { id: order-summary } }` | diff a screenshot — or one element's region — against a stored baseline |
36
- | `run` | `- run: login` | execute another flow's steps inline (fragment or e2e) |
37
- | `when` | `- when: { visible: "What's new" }` + `steps: [...]` | run a guarded step block only when the condition holds (no else) |
23
+ | Directive | YAML | Meaning |
24
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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`, `- tap: { x: 0.5, y: 0.57 }`, `- tap: { on: Login, times: 2 }`, `- tap: { on: { x: 0.5, y: 0.57 }, times: 2 }` | tap by selector (auto-waits) or raw point; `times` (2 = double-tap) needs the target nested under `on:` — a selector or a point (`{ x, y, times }` is rejected) |
27
+ | `long-press` | `- long-press: Row 3`, `- long-press: { x: 0.5, y: 0.6 }`, `- long-press: { on: <sel>, duration: 1200 }`, `- long-press: { on: { x: 0.5, y: 0.6 }, duration: 1200 }` | press and hold an element or raw point (default 800ms; Chromium: mouse press-hold); `duration` needs the target nested under `on:` — a selector or a point |
28
+ | `type` | `- type: { into: email, text: "a@b.com" }` | focus a field, type, then press Enter to submit + dismiss the keyboard |
29
+ | `scroll-to` | `- scroll-to: "Order #1234"` (scrolls down) or `- scroll-to: { target: …, direction: right, within: … }` | momentum-free scroll until the target is visible |
30
+ | `pinch` | `- pinch: { on: "Map", scale: 3 }` or `- pinch: { scale: 0.5 }` | two-finger zoom in (`scale` > 1) or out (`< 1`); big scales chain gestures; `on` optional — defaults to screen center; open-loop — assert the visible result |
31
+ | `rotate` | `- rotate: { on: "Map", by: 90 }` or `- rotate: { by: -45 }` | two-finger rotation by degrees (+ CW, − CCW, within ±3000°; options map only); `on` optional — screen center default; not `tool: rotate` (orientation) |
32
+ | `await` | `- await: { visible: Home }` | wait for a UI condition |
33
+ | `wait` | `- wait: 500` | pause for a fixed number of milliseconds (last resort — prefer `await`) |
34
+ | `assert` | `- assert: { visible: Welcome }` | check a condition, hard-fail if it never holds |
35
+ | `snapshot` | `- snapshot: home` or `- snapshot: { name: home, maxMismatch: 0.5, cropOn: { id: order-summary } }` | diff a screenshot — or one element's region — against a stored baseline |
36
+ | `run` | `- run: login.yaml` | execute another flow's steps inline (fragment or e2e); a YAML path resolved relative to the flow file that contains the step (e.g. `../shared/login.yaml`); `.yaml` is optional (`run: login` = `login.yaml` beside the flow) |
37
+ | `when` | `- when: { visible: "What's new" }` + `steps: [...]` | run a guarded step block only when the condition holds (no else) |
38
38
 
39
39
  ### Selectors
40
40
 
@@ -118,7 +118,11 @@ Since a `tv-remote` path is positional (like a coordinate tap), gate each naviga
118
118
 
119
119
  ### Standalone runner
120
120
 
121
- `argent flow run <name> [--device <id>] [--platform ios|android|chromium|vega] [--update-baselines] [--output <dir>] [--json]` runs a flow with no LLM in the loop and exits non-zero on any failure — suitable for CI (e2e flows; a fragment runs against the current device state, useful while authoring). `snapshot` baselines live in `.argent/flows/__baselines__/<flow>/`, keyed by platform + resolution; a `snapshot` step **fails** when no baseline exists for the run's device class, so seed baselines with `--update-baselines` and have the user review and commit `__baselines__/` — and pin the device class in CI (`--device`/`--platform`, same simulator model) so runs compare against the committed key. The status bar is pinned (iOS `simctl status_bar`, Android demo mode) for the run so it doesn't drive visual diffs. `--output <dir>` writes each failed snapshot's baseline/current/diff images to `<dir>/<flow>/` — a stable path for CI artifact upload.
121
+ `argent flow run <flow|flow.yaml|dir> [--device <id>] [--platform ios|android|chromium|vega] [--update-baselines] [--output <dir>] [-r|--recursive] [--json]` runs a flow with no LLM in the loop and exits non-zero on any failure — suitable for CI (e2e flows; a fragment runs against the current device state, useful while authoring). The argument is either a saved flow's name, read from `.argent/flows/<name>.yaml` under the current directory, or a `.yaml` file path (relative to the current directory, or absolute) for a flow kept anywhere else. The two never collide: a name carries no separator and no extension, so an argument ending in `.yaml` is always a path and never falls back to the flows directory. Either way the filename (minus `.yaml`) names the run's report and artifacts, so it must contain only letters, numbers, `_`, or `-`. A directory path runs every flow in it sequentially, printing only failing steps plus a final `passed/failed/skipped` flow summary (`--json` prints one aggregate object); `-r`/`--recursive` walks subdirectories too, skipping dot-directories and `node_modules`. An invalid flow file fails alone and the batch continues; an infra error stops the batch, counting the remaining flows skipped. Only a path reaches a directory — a name always resolves to one `.yaml` file. `argent flow list` prints runnable paths for flows saved under `.argent/flows/` — a nested one is addressable by its path only.
122
+
123
+ The standalone command uses only the auto-started local tool server. It is unavailable while `ARGENT_TOOLS_URL` or `argent link` routing is active; unset `ARGENT_TOOLS_URL` or run `argent unlink` first. This restriction applies only to the CLI: an agent may continue calling `flow-execute` with `name` and `project_root`, including through a remote tool server — but a remote call uploads the one YAML into a temp directory on the server, and both `run:` targets (whose referenced files stay behind on the client) and `__baselines__/` resolve beside that copy, so only a self-contained flow replays remotely (see _Replaying_).
124
+
125
+ `snapshot` baselines live beside the **real** (symlink-resolved) **top-level** flow file in `<canonical-root-flow-directory>/__baselines__/<real-root-flow-filename-minus-.yaml>/` — directory _and_ key both come from the resolved file (for a saved flow that is a regular file, simply `.argent/flows/__baselines__/<flow>/`; for one that is a symlink, `.argent/flows/smoke.yaml` → `../vault/a-smoke.yaml` keys `../vault/__baselines__/a-smoke/`, so two projects symlinking their own `smoke.yaml` into one shared vault keep separate baselines — commit baselines beside the real file under the real file's name, and move any set already committed under `.argent/flows/__baselines__/<flow>/` there once) — snapshots inside composed fragments are keyed by and stored with the root flow, wherever the fragment file lives — keyed by platform + resolution; a `snapshot` step **fails** when no baseline exists for the run's device class, so seed baselines with `--update-baselines` and have the user review and commit `__baselines__/` — and pin the device class in CI (`--device`/`--platform`, same simulator model) so runs compare against the committed key. The status bar is pinned (iOS `simctl status_bar`, Android demo mode) for the run so it doesn't drive visual diffs. `--output <dir>` writes each failed snapshot's baseline/current/diff images to `<dir>/<flow>/` — a stable path for CI artifact upload. When two different flow files share a filename and one `--output` dir, the later export lands in `<dir>/<flow>-<pathhash>/` instead (deterministic per flow path, with a warning) so neither flow's evidence overwrites the other's.
122
126
 
123
127
  ## Tools
124
128
 
@@ -128,8 +132,8 @@ Since a `tv-remote` path is positional (like a coordinate tap), gate each naviga
128
132
  | `flow-add-step` | Execute a tool call live and record it if it succeeds |
129
133
  | `flow-add-echo` | Add a label/comment that prints during replay |
130
134
  | `flow-finish-recording` | Stop recording and get a summary |
131
- | `flow-read-prerequisite` | Read a flow's execution prerequisite without running it |
132
- | `flow-execute` | Replay a saved flow by name |
135
+ | `flow-read-prerequisite` | Read a flow's execution prerequisite without running it (same `name`/`flow_path` sources) |
136
+ | `flow-execute` | Replay a flow — a saved one by `name`, or any flow YAML by absolute `flow_path` |
133
137
 
134
138
  Every tool during recording returns the current flow file contents, so you can track what has been recorded. Rules:
135
139
 
@@ -166,7 +170,7 @@ Record an `await-ui-element` step to **gate** the next step on a screen transiti
166
170
  - `tool: gesture-pinch` → `pinch: { on: "<target>", scale: … }`, deriving `scale` as `endDistance / startDistance`. Set `on:` to the element under the pinch center when the pinch was aimed at one (the map or image being zoomed); omit it for a screen-center pinch. Don't carry the recorded distances/angle over — the directive re-derives the geometry (finger placement, system-edge avoidance, chaining of large scales) at run time, so the conversion swaps device-specific coordinates for a portable selector with auto-wait. Keep the raw `tool: gesture-pinch` step when the pinch is anchored at a specific point _inside_ a large element (zooming toward a particular map location, not the map's center) or deliberately pans via `endCenterX`/`endCenterY` — `on:` takes only a selector and re-centers the pinch on the element's frame center, so converting would silently move the zoom anchor.
167
171
  - `tool: gesture-rotate` → `rotate: { on: "<target>", by: … }`, deriving `by` as `endAngle − startAngle` (the tool's `endAngle` > `startAngle` turns clockwise, matching the directive's positive `by`). Set `on:` to the element under the rotation center when the rotation was aimed at one (the map or image being rotated); omit it for a screen-center rotation. Don't carry the recorded `centerX`/`centerY`, radii (`radius` or `radiusX`/`radiusY`), `startAngle`, or `durationMs` over — the directive re-derives the geometry (finger placement, physical-circle radius, system-edge avoidance) and runs at a fixed pace (~90° per 300 ms), so the conversion swaps device-specific coordinates for a portable selector with auto-wait. Keep the raw `tool: gesture-rotate` step when the rotation is anchored at a specific point _inside_ a large element rather than its center (the directive re-centers on the element's frame center, so converting would silently move the pivot), when the gesture's speed itself matters (the directive's pace is fixed), or when the sweep exceeds the directive's ±3000° bound.
168
172
 
169
- Every other recorded tool (a velocity-dependent `gesture-swipe`, a fixed-distance `gesture-scroll` not aimed at an element, `button`, `screenshot`, …) has no directive form — leave it as a `tool:` step. The recorder already handles the rest: coordinate `gesture-tap`s are captured as portable `tap:` selector steps, a `restart-app` is captured as a `launch:` step, a `flow-execute` of a sibling fragment is captured as a `run: <name>` composition directive, and device ids are stripped. Captured selectors are emitted in the strict map form (`tap: { text: General }`), never as a loose bare string — the recorder verified the exact element the tap hit, and a bare string would re-parse as loose and route through the identifier-first fallback it was never checked against. After editing, re-run with `flow-execute` to confirm the cleaned flow still passes.
173
+ Every other recorded tool (a velocity-dependent `gesture-swipe`, a fixed-distance `gesture-scroll` not aimed at an element, `button`, `screenshot`, …) has no directive form — leave it as a `tool:` step. The recorder already handles the rest: coordinate `gesture-tap`s are captured as portable `tap:` selector steps, a `restart-app` is captured as a `launch:` step, a `flow-execute` of a sibling fragment is captured as a `run: <name>.yaml` composition directive, and device ids are stripped. Captured selectors are emitted in the strict map form (`tap: { text: General }`), never as a loose bare string — the recorder verified the exact element the tap hit, and a bare string would re-parse as loose and route through the identifier-first fallback it was never checked against. After editing, re-run with `flow-execute` to confirm the cleaned flow still passes.
170
174
 
171
175
  ### Example session
172
176
 
@@ -187,7 +191,7 @@ Then polish the saved file: the two `await-ui-element` steps become `await:` dir
187
191
 
188
192
  ## Replaying
189
193
 
190
- Call `flow-execute` with the flow name (and `project_root`, unless a recording this session already stored it). If the flow has an execution prerequisite, the tool returns a **notice** with the prerequisite text instead of running — verify the prerequisite is met (you can also inspect it beforehand with `flow-read-prerequisite`) and call `flow-execute` again with `prerequisiteAcknowledged: true`. A flow without a prerequisite runs immediately. The run executes all steps in order and returns a structured report: `{ ok, passed, failed, skipped, errored, steps }`.
194
+ Call `flow-execute` with exactly one flow source: `name` for a flow saved under `.argent/flows/` (this form also works through a remote tool server), or `flow_path` — an absolute path to any flow `.yaml`. A flow's `run:` targets and `__baselines__/` resolve on the **tool server's** filesystem, beside the YAML it actually reads. `flow_path` requires the agent and the tool server to share a filesystem and is refused when they don't; `name` is what still runs then, but it is not a way to keep siblings and baselines — a remote call reaches the server as an upload of that one YAML into a fresh temp directory, so a `run:` target errors as a missing fragment and a `snapshot` step fails for a missing baseline under a temp path (and `updateBaselines` writes the baseline there, to be deleted with the directory). Remotely, replay self-contained flows; a flow that composes or snapshots needs the agent and the tool server on one filesystem. Pass `project_root` too — it is always required here; the stored-for-the-session shortcut applies only to the recording tools. If the flow has an execution prerequisite, the tool returns a **notice** with the prerequisite text instead of running — verify the prerequisite is met (you can also inspect it beforehand with `flow-read-prerequisite`, which takes the same `name`/`flow_path` pair) and call `flow-execute` again with `prerequisiteAcknowledged: true`. A flow without a prerequisite runs immediately. The run executes all steps in order and returns a structured report: `{ ok, passed, failed, skipped, errored, steps }`.
191
195
 
192
196
  **What each step reports.** Raw `tool:` steps include the underlying tool's full `result` (screenshots and other outputs render as usual). The directive steps are summarized: `tap`/`type`/`await`/`assert` report only `status` + `reason`, and `snapshot` adds `artifacts` only when there is something to look at — a failed comparison (baseline/current/diff paths), a missing-baseline failure (`current` only), or a baseline write; a clean pass reports just `status` + `reason`. So converting a `tool: gesture-tap` into a `tap:` directive during cleanup drops only that tap's (uninteresting) raw result — output-bearing tools like `screenshot` have no directive form and stay `tool:` steps, so their results keep flowing through.
193
197