@petukhovart/agent-view 0.12.0 → 0.13.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.
@@ -0,0 +1,55 @@
1
+ /** What the arm script reports back from the page. */
2
+ export var TauriShimResult;
3
+ (function (TauriShimResult) {
4
+ TauriShimResult["Armed"] = "armed";
5
+ /** The window is not a Tauri webview — nothing to patch, and nothing is wrong. */
6
+ TauriShimResult["NoTauri"] = "no-tauri";
7
+ })(TauriShimResult || (TauriShimResult = {}));
8
+ const STORE_KEY = '__agentViewTauriDialog__';
9
+ /**
10
+ * Tauri opens its file dialog in Rust, past the webview, so CDP cannot see it
11
+ * and cannot close it. The one seam is the JS side of the call:
12
+ * `@tauri-apps/plugin-dialog` resolves `window.__TAURI_INTERNALS__.invoke` at
13
+ * call time, so replacing that function answers the dialog before the Rust side
14
+ * ever runs. The patch survives repeated arming but not a navigation — a fresh
15
+ * document has a fresh `window`, and the window must be armed again.
16
+ */
17
+ export function buildTauriArmScript(arm) {
18
+ return `(() => {
19
+ const internals = window.__TAURI_INTERNALS__;
20
+ if (!internals || typeof internals.invoke !== 'function') return ${JSON.stringify(TauriShimResult.NoTauri)};
21
+ let store = window[${JSON.stringify(STORE_KEY)}];
22
+ // Disarming a window that was never armed must not install the patch: a
23
+ // command whose whole purpose is to remove interference cannot add some.
24
+ if (!store && ${JSON.stringify(arm === null)}) return ${JSON.stringify(TauriShimResult.NoTauri)};
25
+ if (!store) {
26
+ store = window[${JSON.stringify(STORE_KEY)}] = { arm: null, fired: [] };
27
+ const original = internals.invoke.bind(internals);
28
+ internals.invoke = function (cmd, args, opts) {
29
+ const arm = store.arm;
30
+ if (arm && (cmd === 'plugin:dialog|open' || cmd === 'plugin:dialog|save')) {
31
+ store.arm = null;
32
+ store.fired.push({ ts: Date.now(), cmd: cmd, cancel: arm.kind === 'cancel' });
33
+ if (store.fired.length > 20) store.fired.shift();
34
+ if (arm.kind === 'cancel') return Promise.resolve(null);
35
+ const multiple = cmd === 'plugin:dialog|open' && !!(args && args.options && args.options.multiple);
36
+ return Promise.resolve(multiple ? arm.files : (arm.files[0] === undefined ? null : arm.files[0]));
37
+ }
38
+ return original(cmd, args, opts);
39
+ };
40
+ }
41
+ store.arm = ${JSON.stringify(arm)};
42
+ return ${JSON.stringify(TauriShimResult.Armed)};
43
+ })()`;
44
+ }
45
+ export function buildTauriStatusScript() {
46
+ return `(() => {
47
+ const store = window[${JSON.stringify(STORE_KEY)}];
48
+ return {
49
+ patched: !!store,
50
+ armed: !!(store && store.arm),
51
+ fired: store ? store.fired : [],
52
+ };
53
+ })()`;
54
+ }
55
+ //# sourceMappingURL=tauri-dialog-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tauri-dialog-shim.js","sourceRoot":"","sources":["../../src/server/tauri-dialog-shim.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,CAAN,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,kCAAe,CAAA;IACf,kFAAkF;IAClF,uCAAoB,CAAA;AACtB,CAAC,EAJW,eAAe,KAAf,eAAe,QAI1B;AAQD,MAAM,SAAS,GAAG,0BAA0B,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA0B;IAC5D,OAAO;;qEAE4D,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC;uBACrF,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;kBAG9B,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,IAAI,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC;;qBAE5E,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;gBAe9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;WACxB,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC;KAC3C,CAAA;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO;yBACgB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;KAM7C,CAAA;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petukhovart/agent-view",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI tool for visual verification of desktop apps (Electron/Tauri) via Chrome DevTools Protocol",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,7 +47,8 @@ agent-view dom --diff # Lines changed since last dom call (+ a
47
47
  ### Interaction
48
48
  ```bash
49
49
  agent-view click <ref> # Click element by ref from dom output
50
- agent-view click --pos 100,200 # Click by coordinates (for canvas)
50
+ agent-view click --filter "Save" # Find element by text and click
51
+ agent-view click --pos 100,200 # Click by coordinates — CANVAS ONLY, see below
51
52
  agent-view click <ref> --double # Double-click (fires dblclick handlers); works with --filter / --pos too
52
53
  agent-view fill <ref> "text" # Type into input field
53
54
  agent-view drag --from <ref> --to <ref> # Drag element to another element by ref
@@ -61,6 +62,100 @@ ref and coordinate (e.g. `--from <ref> --to-pos 400,300`). For canvas/Pixi targe
61
62
  Refs are resolved fresh on each call, so window resizes between snapshots are tolerated.
62
63
  Increase `--steps` for handlers using `globalpointermove` so intermediate frames are not skipped.
63
64
 
65
+ **Coordinates are a last resort.** `--pos` / `--from-pos` / `--to-pos` exist for canvas and WebGL,
66
+ where no ref exists. On DOM the correct order is `dom --filter "<text>"` → take the `[ref=N]` →
67
+ `click <ref>`, or `click --filter "<text>"` in one step. A coordinate pair breaks on any layout
68
+ shift, scroll, zoom, or window resize, and it clicks whatever now sits at that point — silently.
69
+ If you reach for `--pos` on a DOM element, first say why the ref was not usable.
70
+
71
+ ### Modals & file pickers (`dialog`, `upload`)
72
+
73
+ A modal that agent-view cannot answer stops a run dead: the window looks frozen, every
74
+ later command times out, and nothing says why. Two kinds, handled differently.
75
+
76
+ **JS modals — `alert` / `confirm` / `prompt` / `beforeunload` — are answered for you.**
77
+ Nothing to set up. While agent-view is attached these never block the page: the default
78
+ standing answer is *dismiss*, and each one is recorded. `agent-view dialog` is the
79
+ reliable place to read that record; the console feed carries the same line
80
+ (`[agent-view] confirm auto-dismissed: <message>`) but only from the moment console
81
+ attaches, so a modal answered before your first `console` call reaches `dialog` alone.
82
+
83
+ ```bash
84
+ agent-view dialog # standing answer + every modal this window has seen
85
+ agent-view dialog policy accept # confirm() → true from now on
86
+ agent-view dialog policy accept --text "name" # prompt() → "name"
87
+ agent-view dialog policy dismiss # back to the default
88
+ agent-view dialog dismiss # answer one that is open right now
89
+ agent-view dialog accept --text "x" # …ditto, accepting
90
+ ```
91
+
92
+ `dialog accept` / `dialog dismiss` exist for a modal that was already open **before**
93
+ agent-view attached — that one produced no event, so no policy applied to it.
94
+
95
+ **Native file pickers never open — you answer them in advance.** Which mechanism applies
96
+ depends on how the app opens the picker, and `dialog arm` sets up all of them at once,
97
+ so you do not have to know:
98
+
99
+ ```bash
100
+ # The input already exists in the DOM (even hidden) — no picker at all, cheapest path
101
+ agent-view upload --selector "#file-input" --file ./fixtures/a.png
102
+ agent-view upload --selector "#imgs" --file ./a.png --file ./b.png # multi-select
103
+ agent-view upload --ref 12 --file ./a.png # if the AX tree exposes it
104
+
105
+ # The input is created inside the click handler, or the app calls a native dialog API
106
+ agent-view dialog arm --file ./fixtures/a.png # then click the button that opens it
107
+ agent-view dialog arm --cancel # act as if the user pressed Cancel
108
+ agent-view dialog disarm # let real pickers open again
109
+ ```
110
+
111
+ Rules that actually bite:
112
+
113
+ - **Arm before the click.** A picker cannot be caught once it is open. `arm` is one-shot —
114
+ it is spent by the first picker and interception turns itself off, so a later click
115
+ opens a real OS dialog.
116
+ - **Click through `agent-view click`, never `eval "el.click()"`.** Chromium refuses to
117
+ open a file picker without user activation, and an eval-driven click carries none: the
118
+ picker is silently dropped and never intercepted.
119
+ - **Hidden inputs have no ref.** `display:none` / `v-show="false"` keeps them out of the
120
+ AX tree, so `dom` never prints one. Use `--selector`. `upload` has no `--filter` on
121
+ purpose: an accessible name lands on the label, not on the input behind it.
122
+ - **`beforeunload` is always dismissed**, whatever the policy — accepting it navigates
123
+ away and loses the state you are checking.
124
+ - Paths are resolved against your cwd and must exist — CDP accepts a bad path silently and
125
+ the app then reads an empty file.
126
+ - `agent-view dialog` after the fact shows what was intercepted and what was answered.
127
+
128
+ Known limits: `showOpenFilePicker()` (File System Access API) exposes no input to fill, so
129
+ it can only be cancelled. Electron does not implement `window.prompt` at all. Native
130
+ dialogs opened straight from an Electron **main** process (`dialog.showOpenDialog` behind
131
+ an IPC channel) are out of reach — CDP does not see the main process.
132
+
133
+ ### Waiting (`wait`)
134
+
135
+ ```bash
136
+ agent-view wait --filter "Saved" # until the text appears in the AX tree
137
+ agent-view wait --filter "Saved" --timeout 20 # max wait in seconds (default 10)
138
+ agent-view wait --filter "Row 5" --window "Main" # specific window
139
+ ```
140
+
141
+ Exits as soon as the element appears; exits non-zero on timeout — so `&&` after it is a real gate.
142
+
143
+ **Never sleep for a fixed time.** No `sleep`, no `timeout`, no `ping -n N 127.0.0.1` as a delay
144
+ (agents reach for `ping` when the harness blocks `sleep` — it is the same mistake in worse
145
+ clothing). A fixed pause is either too short, and you assert against a half-rendered UI, or too
146
+ long, and you burn wall-clock on every step. agent-view has a condition-based wait for every kind
147
+ of signal:
148
+
149
+ | You are waiting for… | Use |
150
+ |---|---|
151
+ | An element to render | `wait --filter "<text>"` |
152
+ | A store/state value | `watch "<expr>" --until "<expr>"` |
153
+ | A log line | `console --follow --until "<pattern>"` |
154
+ | A request to fire | `network --follow --until "<url>"` |
155
+
156
+ If none of these fits, poll: `dom --filter X --count` in a loop with an explicit attempt cap, and
157
+ report how many attempts it took.
158
+
64
159
  ### Screenshots
65
160
  ```bash
66
161
  agent-view screenshot --scale 0.5 # Recommended: JPEG at half-res (~3× fewer vision tokens)
@@ -239,6 +334,7 @@ Verifications cost very different amounts. Pick the cheapest tool that can actua
239
334
  | Count of matching elements | `dom --filter X --count` | Single integer, no tree output, no ref mutations |
240
335
  | App state, store contents, computed values | `eval "expr"` | DOM doesn't expose JS state; reading the tree to infer it is wasteful and unreliable |
241
336
  | Does `window.X` / a globally-exposed API exist? | `eval "typeof window.X"` | DOM doesn't show JS globals; only authoritative check |
337
+ | An element that has not rendered yet | `wait --filter "<text>"` | Exits on appearance and non-zero on timeout — a real gate, unlike a fixed pause |
242
338
  | State *trajectory* — what changed during/after an action | `watch "expr" --until …` or `--max-changes 1` | `eval` shows the final snapshot only; `watch` shows the diffs in order |
243
339
  | Worker logic (SharedWorker / ServiceWorker) | `eval --target <name>` | Workers have no DOM at all |
244
340
  | Did the last action throw or warn? | `console --clear` before, `console --level error,warn` after | Catches errors that don't surface in the DOM |
@@ -247,6 +343,9 @@ Verifications cost very different amounts. Pick the cheapest tool that can actua
247
343
  | Layout, spacing, full-window visual regression | `screenshot --scale 0.5` | The only tool that sees pixels — but expensive (~6k tokens), use last |
248
344
  | Canvas/WebGL scene contents | `scene --diff` | DOM is empty for canvas apps |
249
345
  | What DOM nodes changed after an interaction | `dom --diff` | Returns only `+`/`-` lines; much cheaper than re-reading the full tree |
346
+ | Selecting a file for an input that exists in the DOM | `upload --selector` | No picker opens at all; works on hidden inputs, which have no ref |
347
+ | Selecting a file when the input appears only mid-click | `dialog arm --file` then `click` | The only way — the input does not exist before the click and is gone after |
348
+ | The window stopped responding after a click | `dialog` | Shows whether a modal was answered, and what the app was told |
250
349
 
251
350
  When two tools could answer the same question, prefer the one higher up the table. A common mistake is screenshotting to check "is the count = 5?" when `eval "store.counter"` returns the number directly for ~50 tokens.
252
351
 
@@ -256,6 +355,22 @@ When two tools could answer the same question, prefer the one higher up the tabl
256
355
 
257
356
  A run can produce one of three outcomes per step: **pass**, **fail**, **requires_visual_review**. There is no fourth bucket called "actually fine, here's why". A failed `Expected:` line is FAIL.
258
357
 
358
+ **How to run the commands themselves:**
359
+
360
+ - **Never discard output.** No `>/dev/null`, no `2>&1` to nowhere, no `| head -1` on a command whose
361
+ failure you have not yet read. Every agent-view command prints either the evidence or the reason
362
+ it failed; a suppressed `click` that matched nothing looks exactly like a successful one.
363
+ - **Chain with `&&`, not newlines.** Newline-separated commands keep running after a failure, so a
364
+ broken first step is followed by three steps acting on the wrong state. `&&` stops at the first
365
+ non-zero exit.
366
+ - **One action, then one check.** `click` is not evidence. The evidence is the `dom --diff`,
367
+ `dom --filter … --count`, `eval`, or `console --level error` you run after it. A block of three
368
+ clicks in a row with no check between them proves nothing about any of them.
369
+ - **Call the `agent-view` binary.** Install it once in the target project
370
+ (`pnpm add -D @petukhovart/agent-view`) and call `agent-view …` or `pnpm exec agent-view …`.
371
+ Do not prefix every call with `npx <package>`: it re-resolves the package on each invocation and
372
+ falls outside this skill's `allowed-tools`, so each call needs a fresh permission prompt.
373
+
259
374
  These heuristics catch real bugs. Skipping them is how a run silently passes while the bug sits in plain sight in the same data:
260
375
 
261
376
  1. **A failed expectation is FAIL.** If output disagrees with what the step expected, mark `fail` and continue. Do not soften the expectation. Do not invent prose explanations inline ("label reuse", "convention", "arithmetic off"). Justifications belong in the bug report after the run, never in the per-step log.
@@ -321,7 +436,7 @@ Tolerance default: a designer's code-review level — flag what they'd notice, i
321
436
  ## Resilience
322
437
 
323
438
  - **Stale refs:** After HMR, navigation, or state change — re-run `dom` for fresh refs before interacting
324
- - **Element not found:** Wait 2s, retry once (render delay after HMR). If still missing — report FAIL
439
+ - **Element not found:** `agent-view wait --filter "<text>" --timeout 5` (covers the render delay after HMR). If it times out — report FAIL. Do not insert a blind pause and retry.
325
440
  - **CDP disconnect:** Run `agent-view discover` to check. If no windows — `agent-view launch` (auto-starts Electron/Browser/Tauri). On `PORT_CONFLICT` — surface PID/process and ask user.
326
441
  - **`CDP_TIMEOUT` error:** the command hit the server-side deadline; cached CDP sessions for that port were dropped, so retry once. Repeated timeouts mean the app's DevTools endpoint is wedged — restart the app.
327
442
  - **Max retries per command:** 2. After that — SKIP scenario step with warning