tauri-agent-tools 0.9.1 → 0.9.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.
- package/.agents/skills/tauri-agent-tools/SKILL.md +8 -3
- package/.agents/skills/tauri-bridge-setup/SKILL.md +1 -1
- package/.agents/skills/tauri-debug-quickstart/SKILL.md +1 -1
- package/README.md +4 -2
- package/dist/commands/interact/select.d.ts +6 -1
- package/dist/commands/interact/select.js +87 -29
- package/dist/commands/interact/select.js.map +1 -1
- package/dist/commands/interact/shared.d.ts +96 -0
- package/dist/commands/interact/shared.js +266 -1
- package/dist/commands/interact/shared.js.map +1 -1
- package/dist/commands/interact/type.d.ts +10 -2
- package/dist/commands/interact/type.js +44 -29
- package/dist/commands/interact/type.js.map +1 -1
- package/dist/schemas/interact.d.ts +58 -0
- package/dist/schemas/interact.js +25 -0
- package/dist/schemas/interact.js.map +1 -1
- package/package.json +4 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tauri-agent-tools
|
|
3
3
|
description: CLI for inspecting and interacting with Tauri desktop apps — DOM queries, screenshots, interaction (click/type/scroll), IPC monitoring, store inspection, structured assertions, plus bridge-free diagnostics (unified cross-layer logs, deep OS process trees, OS logs, app paths, sidecar NDJSON tap/replay, forensic bundles) and the `diagnose`/`bundle` super-commands for one-shot triage. Works against older/vendored bridges by degrading gracefully.
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
tags: [tauri, desktop, debugging, screenshot, dom, inspection, diff, mutations, snapshot, interaction, click, type, scroll, invoke, probe, capture, check, store-inspect, logs, bundle, process-tree, forensics, os-logs, app-paths, sidecar, config-inspect, diagnose]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -269,6 +269,9 @@ tauri-agent-tools type "#search" "hello world" --json
|
|
|
269
269
|
# Clear and retype
|
|
270
270
|
tauri-agent-tools type "#email" "new@email.com" --clear --json
|
|
271
271
|
|
|
272
|
+
# App applies the value on a transition/async render: wait longer for it to stick
|
|
273
|
+
tauri-agent-tools type "#q" "term" --verify-timeout 2000 --json
|
|
274
|
+
|
|
272
275
|
# Scroll to bottom
|
|
273
276
|
tauri-agent-tools scroll --to-bottom --json
|
|
274
277
|
|
|
@@ -292,6 +295,8 @@ tauri-agent-tools invoke get_release_context --json
|
|
|
292
295
|
tauri-agent-tools invoke save_item '{"id": 42}' --json
|
|
293
296
|
```
|
|
294
297
|
|
|
298
|
+
`type` and `select <value>` write through the element's native `value` setter (so React/Vue/Svelte controlled inputs see the change); `select --toggle` performs a native `click()`. Both then re-read the element. `verification: "reverted"` in the failure JSON (exit 1, with a `hint`; `type` prints it with `--json`, `select` always) means the app rolled the write back — a controlled input that rejected it — and is the app's answer, not a tooling error. `verification: "transformed"` means the app reformatted the value and counts as success (`verified: false`).
|
|
299
|
+
|
|
295
300
|
### Probe, capture, and check (workflow commands)
|
|
296
301
|
|
|
297
302
|
```bash
|
|
@@ -347,11 +352,11 @@ tauri-agent-tools eval "document.title" --window-label overlay --json
|
|
|
347
352
|
| `mutations` | `<selector>`, `--attributes`, `--duration <ms>`, `--json` | yes | Watch DOM mutations |
|
|
348
353
|
| `snapshot` | `-o <prefix>`, `-s <css>`, `--window-id <id>`, `--dom-depth <n>`, `--eval <js>`, `--json` | yes | Screenshot + DOM + page state + storage |
|
|
349
354
|
| `click` | `<selector>`, `--double`, `--right`, `--wait <ms>`, `--json` | yes | Click a DOM element |
|
|
350
|
-
| `type` | `<selector> <text>`, `--clear`, `--json` | yes | Type text into an input |
|
|
355
|
+
| `type` | `<selector> <text>`, `--clear`, `--verify-timeout <ms>`, `--json` | yes | Type text into an input (native setter, verified write) |
|
|
351
356
|
| `scroll` | `--selector <css>`, `--by <px>`, `--to-top`, `--to-bottom`, `--into-view`, `--json` | yes | Scroll window or element |
|
|
352
357
|
| `focus` | `<selector>`, `--json` | yes | Focus a DOM element |
|
|
353
358
|
| `navigate` | `<target>`, `--json` | yes | Navigate within the app |
|
|
354
|
-
| `select` | `<selector> [value]`, `--toggle`, `--json` | yes | Select dropdown or toggle checkbox |
|
|
359
|
+
| `select` | `<selector> [value]`, `--toggle`, `--verify-timeout <ms>`, `--json` | yes | Select dropdown or toggle checkbox (native setter; `--toggle` via native `click()`; verified write) |
|
|
355
360
|
| `invoke` | `<command> [args-json]`, `--json` | yes | Invoke a Tauri IPC command |
|
|
356
361
|
| `probe` | `--pid <n>`, `--json` | optional | Discover targets and bridge health |
|
|
357
362
|
| `capture` | `-o <dir>`, `-s <css>`, `--window-id <id>`, `--logs-duration <ms>`, `--json` | yes | Full debug evidence bundle |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tauri-bridge-setup
|
|
3
3
|
description: How to add the tauri-agent-tools Rust dev bridge to a Tauri application
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
tags: [tauri, rust, bridge, setup, integration, multi-window, process-tree, capabilities, devtools, health]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tauri-debug-quickstart
|
|
3
3
|
description: First-30-seconds triage for a broken Tauri desktop app. Pick the right command for the symptom you're seeing, with one-line escalations to deeper skills.
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
tags: [tauri, debugging, triage, quickstart, decision-tree, logs, process-tree, bundle, forensics, diagnose]
|
|
6
6
|
---
|
|
7
7
|
|
package/README.md
CHANGED
|
@@ -258,14 +258,16 @@ Monitor Rust backend logs and sidecar output in real-time. Unlike `console-monit
|
|
|
258
258
|
|
|
259
259
|
Interaction commands dispatch DOM events inside the webview. They require the dev bridge (debug builds only).
|
|
260
260
|
|
|
261
|
+
`type` and `select` write through the element's native prototype `value` setter — React's per-instance value tracker turns a plain `el.value = …` into a no-op — and dispatch bubbling `input`/`change` events, so React, Vue, and Svelte controlled inputs update their state; `select --toggle` performs a native `click()`. Both then re-read the element: a value the app rolled back fails with `Value reverted` (`Checked state reverted` for `--toggle`; exit 1 with the reason and a `hint` on stderr, and the failure object with `verification: "reverted"` on stdout — with `--json` for `type`, always for `select`) — treat that as the app's answer, not a tooling error — while a value the app reformatted is a success reported with `verification: "transformed"`. `--verify-timeout <ms>` (default 500) tunes the re-read deadline for apps that apply values asynchronously.
|
|
262
|
+
|
|
261
263
|
| Command | Description |
|
|
262
264
|
|---------|-------------|
|
|
263
265
|
| `click <selector>` | Click a DOM element (`--double`, `--right`, `--wait <ms>`) |
|
|
264
|
-
| `type <selector> <text>` | Type text into an input (`--clear` to empty first) |
|
|
266
|
+
| `type <selector> <text>` | Type text into an input via the native value setter, then verify the write (`--clear` to empty first, `--verify-timeout <ms>`) |
|
|
265
267
|
| `scroll` | Scroll window or element (`--by <px>`, `--to-top`, `--to-bottom`, `--into-view`) |
|
|
266
268
|
| `focus <selector>` | Focus a DOM element |
|
|
267
269
|
| `navigate <target>` | Navigate within the app (route path or URL) |
|
|
268
|
-
| `select <selector> [value]` | Select dropdown value or toggle checkbox (`--toggle
|
|
270
|
+
| `select <selector> [value]` | Select a dropdown value or toggle a checkbox via native `click()`, then verify the write (`--toggle`, `--verify-timeout <ms>`) |
|
|
269
271
|
| `invoke <command> [args-json]` | Invoke a Tauri IPC command |
|
|
270
272
|
|
|
271
273
|
### Workflow Commands
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
2
|
+
import type { VerifyOptions } from './shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* Value mode writes through the native prototype value setter and verifies the
|
|
5
|
+
* write (see buildSetValueScript); toggle mode performs a native click().
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildSelectScript(selector: string, value?: string, toggle?: boolean, options?: VerifyOptions): string;
|
|
3
8
|
export declare function registerSelect(program: Command): void;
|
|
@@ -1,37 +1,83 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { resolveBridge } from '../shared.js';
|
|
3
|
-
import { addInteractOptions, escapeSelector } from './shared.js';
|
|
3
|
+
import { addInteractOptions, addVerifyOptions, buildSetValueScript, escapeSelector, parseInteractResult, resolveVerifyTimeout, VERIFY_INTERVAL_MS, VERIFY_POLL_SNIPPET, } from './shared.js';
|
|
4
4
|
import { SelectResultSchema } from '../../schemas/interact.js';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* --toggle: a native click(). The browser flips checkedness internally (not
|
|
7
|
+
* through the JS `checked` setter React wraps), fires input + change, honours
|
|
8
|
+
* preventDefault(), and is the only event React's ChangeEventPlugin observes
|
|
9
|
+
* for checkbox/radio. click() is a no-op on disabled controls (including
|
|
10
|
+
* descendants of <fieldset disabled>) and cannot uncheck a radio, so those
|
|
11
|
+
* are refused up front instead of reporting a state that did not change.
|
|
12
|
+
*/
|
|
13
|
+
function buildToggleScript(selector, verifyTimeoutMs) {
|
|
6
14
|
const escaped = escapeSelector(selector);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
15
|
-
return JSON.stringify({ success: true, selector: '${escaped}', tagName: el.tagName.toLowerCase(), checked: el.checked });
|
|
16
|
-
} catch (e) {
|
|
17
|
-
return JSON.stringify({ success: false, error: String(e) });
|
|
15
|
+
return `(() => {${VERIFY_POLL_SNIPPET}
|
|
16
|
+
var selector = '${escaped}';
|
|
17
|
+
var fail = function (extra) { return JSON.stringify(Object.assign({ success: false, selector: selector }, extra)); };
|
|
18
|
+
function isDisabled(node) {
|
|
19
|
+
try { if (node.matches(':disabled')) return true; } catch (e) { /* selector unsupported */ }
|
|
20
|
+
if (node.disabled) return true;
|
|
21
|
+
return !!(typeof node.closest === 'function' && node.closest('fieldset[disabled]'));
|
|
18
22
|
}
|
|
19
|
-
})()`;
|
|
20
|
-
}
|
|
21
|
-
const escapedValue = value !== undefined ? escapeSelector(value) : '';
|
|
22
|
-
return `(function() {
|
|
23
23
|
try {
|
|
24
|
-
var el = document.querySelector(
|
|
25
|
-
if (!el) return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
var el = document.querySelector(selector);
|
|
25
|
+
if (!el) return fail({ error: 'Element not found: ' + selector });
|
|
26
|
+
var tagName = el.tagName.toLowerCase();
|
|
27
|
+
var inputType = tagName === 'input' ? String(el.type || 'text').toLowerCase() : '';
|
|
28
|
+
if (inputType !== 'checkbox' && inputType !== 'radio') {
|
|
29
|
+
return fail({ tagName: tagName, error: 'Element <' + tagName + (inputType ? ' type=' + inputType : '') + '> is not a checkbox or radio', hint: '--toggle supports <input type=checkbox|radio>; use \`select <selector> <value>\` for other elements.' });
|
|
30
|
+
}
|
|
31
|
+
var before = !!el.checked;
|
|
32
|
+
if (isDisabled(el)) {
|
|
33
|
+
return fail({ tagName: tagName, checked: before, error: 'Element is disabled (or inside a disabled <fieldset>); click() has no effect' });
|
|
34
|
+
}
|
|
35
|
+
if (inputType === 'radio' && before) {
|
|
36
|
+
return fail({ tagName: tagName, checked: true, error: 'Radio input is already checked; a radio cannot be unchecked by clicking it', hint: 'Toggle the sibling radio you want selected instead.' });
|
|
37
|
+
}
|
|
38
|
+
var expected = !before;
|
|
39
|
+
el.click();
|
|
40
|
+
return __verify(function () {
|
|
41
|
+
var cur = document.querySelector(selector) || (el.isConnected ? el : null);
|
|
42
|
+
return cur ? !!cur.checked : null;
|
|
43
|
+
}, expected, ${verifyTimeoutMs}, ${VERIFY_INTERVAL_MS}).then(function (v) {
|
|
44
|
+
if (v.ok) {
|
|
45
|
+
return JSON.stringify({ success: true, selector: selector, tagName: tagName, checked: v.observed, previousChecked: before, verified: true, verification: 'matched' });
|
|
46
|
+
}
|
|
47
|
+
if (v.observed === null) {
|
|
48
|
+
return fail({
|
|
49
|
+
tagName: tagName, previousChecked: before, verified: false,
|
|
50
|
+
error: 'Element left the document after click(); the checked state could not be verified',
|
|
51
|
+
hint: 'The app removed or re-rendered the element without a matching selector after the click (navigation, a remount, or the click consumed the item). Re-query the DOM to confirm the app state.'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return fail({
|
|
55
|
+
tagName: tagName, checked: !!v.observed, previousChecked: before, verified: false, verification: 'reverted',
|
|
56
|
+
error: 'Checked state reverted: click() set checked=' + expected + ' but the element reads ' + String(!!v.observed) + ' after ${verifyTimeoutMs}ms',
|
|
57
|
+
hint: 'A controlled checkbox whose change handler did not accept the change, or a click listener that called preventDefault().'
|
|
58
|
+
});
|
|
59
|
+
});
|
|
30
60
|
} catch (e) {
|
|
31
|
-
return
|
|
61
|
+
return fail({ error: 'Script error: ' + (e && e.message ? e.message : String(e)) });
|
|
32
62
|
}
|
|
33
63
|
})()`;
|
|
34
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Value mode writes through the native prototype value setter and verifies the
|
|
67
|
+
* write (see buildSetValueScript); toggle mode performs a native click().
|
|
68
|
+
*/
|
|
69
|
+
export function buildSelectScript(selector, value, toggle = false, options) {
|
|
70
|
+
const verifyTimeoutMs = resolveVerifyTimeout(options);
|
|
71
|
+
if (toggle)
|
|
72
|
+
return buildToggleScript(selector, verifyTimeoutMs);
|
|
73
|
+
return buildSetValueScript(selector, value ?? '', {
|
|
74
|
+
focus: false,
|
|
75
|
+
clear: false,
|
|
76
|
+
lowerCaseTagName: true,
|
|
77
|
+
notFoundErrorExpr: `'Element not found: ' + selector`,
|
|
78
|
+
verifyTimeoutMs,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
35
81
|
export function registerSelect(program) {
|
|
36
82
|
const cmd = new Command('select')
|
|
37
83
|
.description('Set the value of a form element or toggle a checkbox')
|
|
@@ -42,17 +88,29 @@ export function registerSelect(program) {
|
|
|
42
88
|
Examples:
|
|
43
89
|
$ tauri-agent-tools select "select#country" "US"
|
|
44
90
|
$ tauri-agent-tools select "input[name='agree']" --toggle
|
|
45
|
-
$ tauri-agent-tools select "input#search" "hello world"
|
|
91
|
+
$ tauri-agent-tools select "input#search" "hello world"
|
|
92
|
+
|
|
93
|
+
Values are written through the native prototype setter and followed by bubbling
|
|
94
|
+
input + change events; <select> values must match an <option> value attribute.
|
|
95
|
+
--toggle performs a native click() so React and other frameworks that listen to
|
|
96
|
+
click for checkboxes update their state. The element is re-read afterwards and
|
|
97
|
+
the command fails when the app reverted the change.`);
|
|
46
98
|
addInteractOptions(cmd);
|
|
99
|
+
addVerifyOptions(cmd);
|
|
47
100
|
cmd.action(async (selector, value, opts) => {
|
|
101
|
+
// Build first: an invalid --verify-timeout fails before any bridge call.
|
|
102
|
+
const script = buildSelectScript(selector, value, opts.toggle ?? false, { verifyTimeoutMs: opts.verifyTimeout });
|
|
48
103
|
const bridge = await resolveBridge(opts);
|
|
49
|
-
const script = buildSelectScript(selector, value, opts.toggle ?? false);
|
|
50
104
|
const raw = await bridge.eval(script);
|
|
51
|
-
const result =
|
|
105
|
+
const result = parseInteractResult(raw, SelectResultSchema, 'Select');
|
|
106
|
+
// select always prints its JSON result; on failure the object (verification,
|
|
107
|
+
// hint, options, …) still goes to stdout, and the thrown error reaches
|
|
108
|
+
// stderr and sets exit code 1.
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|
|
52
110
|
if (!result.success) {
|
|
53
|
-
|
|
111
|
+
const hint = result.hint ? `\n hint: ${result.hint}` : '';
|
|
112
|
+
throw new Error(`${result.error ?? 'Select failed'}${hint}`);
|
|
54
113
|
}
|
|
55
|
-
console.log(JSON.stringify(result, null, 2));
|
|
56
114
|
});
|
|
57
115
|
program.addCommand(cmd);
|
|
58
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/commands/interact/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/commands/interact/select.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
import type { z } from 'zod';
|
|
2
3
|
/**
|
|
3
4
|
* Escapes backslashes and single quotes in CSS selectors for safe
|
|
4
5
|
* embedding inside JS string literals wrapped in single quotes.
|
|
@@ -21,3 +22,98 @@ export declare function buildWaitAndFindScript(selector: string, waitMs: number)
|
|
|
21
22
|
* plus --json for machine-readable output. Returns the command for chaining.
|
|
22
23
|
*/
|
|
23
24
|
export declare function addInteractOptions(cmd: Command): Command;
|
|
25
|
+
/**
|
|
26
|
+
* Default deadline for the post-write re-read. An accepted write resolves on
|
|
27
|
+
* the first (synchronous) check, so it costs nothing; only a rejected write
|
|
28
|
+
* waits out the whole deadline.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_VERIFY_TIMEOUT_MS = 500;
|
|
31
|
+
/**
|
|
32
|
+
* dev_bridge.rs answers 504 after 5 s and BridgeClient.eval aborts at 5 s;
|
|
33
|
+
* keep at least 1 s of slack for dispatch + IPC + HTTP.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MAX_VERIFY_TIMEOUT_MS = 4000;
|
|
36
|
+
/**
|
|
37
|
+
* Poll cadence after the synchronous first check. React commits
|
|
38
|
+
* transition-lane updates on a Scheduler macrotask, so a microtask/rAF
|
|
39
|
+
* re-read would be too early.
|
|
40
|
+
*/
|
|
41
|
+
export declare const VERIFY_INTERVAL_MS = 50;
|
|
42
|
+
export interface VerifyOptions {
|
|
43
|
+
/** 0..MAX_VERIFY_TIMEOUT_MS; 0 = single synchronous check. Default DEFAULT_VERIFY_TIMEOUT_MS. */
|
|
44
|
+
verifyTimeoutMs?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function resolveVerifyTimeout(opts?: VerifyOptions): number;
|
|
47
|
+
/** Adds --verify-timeout to a value-writing interaction command. */
|
|
48
|
+
export declare function addVerifyOptions(cmd: Command): Command;
|
|
49
|
+
/**
|
|
50
|
+
* JS snippet defining `__resolveValueWriter(node)`.
|
|
51
|
+
*
|
|
52
|
+
* React (react-dom's inputValueTracking) redefines `value` as an own accessor
|
|
53
|
+
* on every mounted <input>/<textarea> instance to remember what it wrote;
|
|
54
|
+
* assigning through that accessor makes the following input/change event a
|
|
55
|
+
* no-op for React (#10). Built-in form elements are therefore written through
|
|
56
|
+
* the prototype setter React itself captured. Custom elements are searched up
|
|
57
|
+
* their prototype chain (Lit/Stencil define accessors on the class prototype)
|
|
58
|
+
* and fall back to plain assignment for instance-defined or class-field
|
|
59
|
+
* `value`s. Anything else has no value setter and is refused instead of being
|
|
60
|
+
* silently given an expando property.
|
|
61
|
+
*
|
|
62
|
+
* The parameter is deliberately named `node` so generated scripts never
|
|
63
|
+
* contain the text `el.value =`.
|
|
64
|
+
*/
|
|
65
|
+
export declare const NATIVE_VALUE_WRITER_SNIPPET = "\n function __resolveValueWriter(node) {\n var tag = node.tagName;\n var proto = tag === 'INPUT' ? HTMLInputElement.prototype\n : tag === 'TEXTAREA' ? HTMLTextAreaElement.prototype\n : tag === 'SELECT' ? HTMLSelectElement.prototype\n : null;\n if (proto) {\n var d = Object.getOwnPropertyDescriptor(proto, 'value');\n if (d && typeof d.set === 'function') return { kind: 'native', set: function (v) { d.set.call(node, v); } };\n return { kind: 'assign', set: function (v) { node.value = v; } };\n }\n if (String(node.localName || '').indexOf('-') !== -1) {\n var p = Object.getPrototypeOf(node);\n while (p && p !== Object.prototype) {\n var cd = Object.getOwnPropertyDescriptor(p, 'value');\n if (cd && typeof cd.set === 'function') return { kind: 'custom', set: function (v) { cd.set.call(node, v); } };\n p = Object.getPrototypeOf(p);\n }\n if ('value' in node) return { kind: 'assign', set: function (v) { node.value = v; } };\n }\n return null;\n }";
|
|
66
|
+
/**
|
|
67
|
+
* JS snippet defining `__verify(read, expected, timeoutMs, intervalMs)`.
|
|
68
|
+
*
|
|
69
|
+
* The first read is synchronous: React restores a rejected controlled value
|
|
70
|
+
* inside dispatchEvent, so it is already visible. Later reads catch
|
|
71
|
+
* transition-lane commits and frameworks that apply values asynchronously.
|
|
72
|
+
* Resolves `{ ok, observed }` and never rejects. `read` is re-invoked on every
|
|
73
|
+
* tick so a re-mounted node is read instead of a stale reference.
|
|
74
|
+
*/
|
|
75
|
+
export declare const VERIFY_POLL_SNIPPET = "\n function __verify(read, expected, timeoutMs, intervalMs) {\n return new Promise(function (resolve) {\n var deadline = Date.now() + timeoutMs;\n function check() {\n var observed;\n try { observed = read(); } catch (e) { observed = undefined; }\n if (observed === expected) { resolve({ ok: true, observed: observed }); return; }\n if (Date.now() >= deadline) { resolve({ ok: false, observed: observed }); return; }\n setTimeout(check, intervalMs);\n }\n check();\n });\n }";
|
|
76
|
+
/**
|
|
77
|
+
* JS snippet defining `__settle()` — resolves after two macrotasks. Used after
|
|
78
|
+
* focus()/--clear so that state the app changed in response (React flushes an
|
|
79
|
+
* onFocus setState on a microtask, an accepted clear commits synchronously, and
|
|
80
|
+
* a commit scheduled from the app's own zero-delay timer lands on a Scheduler
|
|
81
|
+
* task after the first macrotask) is visible before the pre-write baseline is
|
|
82
|
+
* read and the target node is re-resolved.
|
|
83
|
+
*/
|
|
84
|
+
export declare const SETTLE_SNIPPET = "\n function __settle() {\n return new Promise(function (resolve) {\n setTimeout(function () { setTimeout(resolve, 0); }, 0);\n });\n }";
|
|
85
|
+
/** JS snippet defining `__selectOptionValues(node)` — option values of a <select>, [] otherwise. */
|
|
86
|
+
export declare const SELECT_OPTIONS_SNIPPET = "\n function __selectOptionValues(node) {\n if (!node.options || typeof node.options.length !== 'number') return [];\n return Array.prototype.map.call(node.options, function (o) { return String(o.value); });\n }";
|
|
87
|
+
export interface SetValueScriptOptions {
|
|
88
|
+
/** `type` focuses the element first; `select` does not. */
|
|
89
|
+
focus: boolean;
|
|
90
|
+
/** `type --clear`: select() + write '' + input event before the real write. */
|
|
91
|
+
clear: boolean;
|
|
92
|
+
/** Existing contracts differ: `type` reports el.tagName as-is, `select` lower-cases it. */
|
|
93
|
+
lowerCaseTagName: boolean;
|
|
94
|
+
/** JS expression for the not-found error; `selector` is in scope. */
|
|
95
|
+
notFoundErrorExpr: string;
|
|
96
|
+
verifyTimeoutMs: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Shared script body for `type` and `select` value mode. Returns an arrow IIFE
|
|
100
|
+
* that resolves (via a Promise — the bridge awaits it) to a JSON string and
|
|
101
|
+
* never throws into the bridge.
|
|
102
|
+
*
|
|
103
|
+
* Sequence: snapshot `previousValue` → [focus] → [clear + input] → settle →
|
|
104
|
+
* re-resolve the target (the app may have re-mounted it) → snapshot `baseline`
|
|
105
|
+
* → native write → `applied` → refuse values the browser discarded or clamped
|
|
106
|
+
* back → input, change → verify. `reverted` means the re-read equals
|
|
107
|
+
* `baseline` or `previousValue`; comparing only against the pre-command value
|
|
108
|
+
* would let an app that accepted the clear (or changed the value on focus) but
|
|
109
|
+
* rejected the real write pass as `transformed`. A field the app empties after
|
|
110
|
+
* a non-empty write is a failure too ("cleared"): the intended value did not
|
|
111
|
+
* land whether the app rejected or consumed it.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildSetValueScript(selector: string, value: string, o: SetValueScriptOptions): string;
|
|
114
|
+
/**
|
|
115
|
+
* Parses an interaction result from the bridge. dev_bridge.rs turns a thrown
|
|
116
|
+
* script into the string "ERROR: <message>", which JSON.parse would reject
|
|
117
|
+
* with an opaque SyntaxError — surface it as an actionable error instead.
|
|
118
|
+
*/
|
|
119
|
+
export declare function parseInteractResult<S extends z.ZodTypeAny>(raw: unknown, schema: S, command: string): z.infer<S>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addBridgeOptions } from '../shared.js';
|
|
1
|
+
import { addBridgeOptions, parseIntArg } from '../shared.js';
|
|
2
2
|
/**
|
|
3
3
|
* Escapes backslashes and single quotes in CSS selectors for safe
|
|
4
4
|
* embedding inside JS string literals wrapped in single quotes.
|
|
@@ -59,4 +59,269 @@ export function buildWaitAndFindScript(selector, waitMs) {
|
|
|
59
59
|
export function addInteractOptions(cmd) {
|
|
60
60
|
return addBridgeOptions(cmd).option('--json', 'Output result as JSON');
|
|
61
61
|
}
|
|
62
|
+
// === Value writing + verification (type, select) ===
|
|
63
|
+
/**
|
|
64
|
+
* Default deadline for the post-write re-read. An accepted write resolves on
|
|
65
|
+
* the first (synchronous) check, so it costs nothing; only a rejected write
|
|
66
|
+
* waits out the whole deadline.
|
|
67
|
+
*/
|
|
68
|
+
export const DEFAULT_VERIFY_TIMEOUT_MS = 500;
|
|
69
|
+
/**
|
|
70
|
+
* dev_bridge.rs answers 504 after 5 s and BridgeClient.eval aborts at 5 s;
|
|
71
|
+
* keep at least 1 s of slack for dispatch + IPC + HTTP.
|
|
72
|
+
*/
|
|
73
|
+
export const MAX_VERIFY_TIMEOUT_MS = 4000;
|
|
74
|
+
/**
|
|
75
|
+
* Poll cadence after the synchronous first check. React commits
|
|
76
|
+
* transition-lane updates on a Scheduler macrotask, so a microtask/rAF
|
|
77
|
+
* re-read would be too early.
|
|
78
|
+
*/
|
|
79
|
+
export const VERIFY_INTERVAL_MS = 50;
|
|
80
|
+
export function resolveVerifyTimeout(opts) {
|
|
81
|
+
const ms = opts?.verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;
|
|
82
|
+
if (!Number.isInteger(ms) || ms < 0 || ms > MAX_VERIFY_TIMEOUT_MS) {
|
|
83
|
+
throw new Error(`--verify-timeout must be an integer between 0 and ${MAX_VERIFY_TIMEOUT_MS} ms (the bridge aborts evals after 5000 ms)`);
|
|
84
|
+
}
|
|
85
|
+
return ms;
|
|
86
|
+
}
|
|
87
|
+
/** Adds --verify-timeout to a value-writing interaction command. */
|
|
88
|
+
export function addVerifyOptions(cmd) {
|
|
89
|
+
return cmd.option('--verify-timeout <ms>', `Max time to wait for the written value to stick before reporting it reverted (max ${MAX_VERIFY_TIMEOUT_MS}, 0 = single check)`, parseIntArg, DEFAULT_VERIFY_TIMEOUT_MS);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* JS snippet defining `__resolveValueWriter(node)`.
|
|
93
|
+
*
|
|
94
|
+
* React (react-dom's inputValueTracking) redefines `value` as an own accessor
|
|
95
|
+
* on every mounted <input>/<textarea> instance to remember what it wrote;
|
|
96
|
+
* assigning through that accessor makes the following input/change event a
|
|
97
|
+
* no-op for React (#10). Built-in form elements are therefore written through
|
|
98
|
+
* the prototype setter React itself captured. Custom elements are searched up
|
|
99
|
+
* their prototype chain (Lit/Stencil define accessors on the class prototype)
|
|
100
|
+
* and fall back to plain assignment for instance-defined or class-field
|
|
101
|
+
* `value`s. Anything else has no value setter and is refused instead of being
|
|
102
|
+
* silently given an expando property.
|
|
103
|
+
*
|
|
104
|
+
* The parameter is deliberately named `node` so generated scripts never
|
|
105
|
+
* contain the text `el.value =`.
|
|
106
|
+
*/
|
|
107
|
+
export const NATIVE_VALUE_WRITER_SNIPPET = `
|
|
108
|
+
function __resolveValueWriter(node) {
|
|
109
|
+
var tag = node.tagName;
|
|
110
|
+
var proto = tag === 'INPUT' ? HTMLInputElement.prototype
|
|
111
|
+
: tag === 'TEXTAREA' ? HTMLTextAreaElement.prototype
|
|
112
|
+
: tag === 'SELECT' ? HTMLSelectElement.prototype
|
|
113
|
+
: null;
|
|
114
|
+
if (proto) {
|
|
115
|
+
var d = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
116
|
+
if (d && typeof d.set === 'function') return { kind: 'native', set: function (v) { d.set.call(node, v); } };
|
|
117
|
+
return { kind: 'assign', set: function (v) { node.value = v; } };
|
|
118
|
+
}
|
|
119
|
+
if (String(node.localName || '').indexOf('-') !== -1) {
|
|
120
|
+
var p = Object.getPrototypeOf(node);
|
|
121
|
+
while (p && p !== Object.prototype) {
|
|
122
|
+
var cd = Object.getOwnPropertyDescriptor(p, 'value');
|
|
123
|
+
if (cd && typeof cd.set === 'function') return { kind: 'custom', set: function (v) { cd.set.call(node, v); } };
|
|
124
|
+
p = Object.getPrototypeOf(p);
|
|
125
|
+
}
|
|
126
|
+
if ('value' in node) return { kind: 'assign', set: function (v) { node.value = v; } };
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}`;
|
|
130
|
+
/**
|
|
131
|
+
* JS snippet defining `__verify(read, expected, timeoutMs, intervalMs)`.
|
|
132
|
+
*
|
|
133
|
+
* The first read is synchronous: React restores a rejected controlled value
|
|
134
|
+
* inside dispatchEvent, so it is already visible. Later reads catch
|
|
135
|
+
* transition-lane commits and frameworks that apply values asynchronously.
|
|
136
|
+
* Resolves `{ ok, observed }` and never rejects. `read` is re-invoked on every
|
|
137
|
+
* tick so a re-mounted node is read instead of a stale reference.
|
|
138
|
+
*/
|
|
139
|
+
export const VERIFY_POLL_SNIPPET = `
|
|
140
|
+
function __verify(read, expected, timeoutMs, intervalMs) {
|
|
141
|
+
return new Promise(function (resolve) {
|
|
142
|
+
var deadline = Date.now() + timeoutMs;
|
|
143
|
+
function check() {
|
|
144
|
+
var observed;
|
|
145
|
+
try { observed = read(); } catch (e) { observed = undefined; }
|
|
146
|
+
if (observed === expected) { resolve({ ok: true, observed: observed }); return; }
|
|
147
|
+
if (Date.now() >= deadline) { resolve({ ok: false, observed: observed }); return; }
|
|
148
|
+
setTimeout(check, intervalMs);
|
|
149
|
+
}
|
|
150
|
+
check();
|
|
151
|
+
});
|
|
152
|
+
}`;
|
|
153
|
+
/**
|
|
154
|
+
* JS snippet defining `__settle()` — resolves after two macrotasks. Used after
|
|
155
|
+
* focus()/--clear so that state the app changed in response (React flushes an
|
|
156
|
+
* onFocus setState on a microtask, an accepted clear commits synchronously, and
|
|
157
|
+
* a commit scheduled from the app's own zero-delay timer lands on a Scheduler
|
|
158
|
+
* task after the first macrotask) is visible before the pre-write baseline is
|
|
159
|
+
* read and the target node is re-resolved.
|
|
160
|
+
*/
|
|
161
|
+
export const SETTLE_SNIPPET = `
|
|
162
|
+
function __settle() {
|
|
163
|
+
return new Promise(function (resolve) {
|
|
164
|
+
setTimeout(function () { setTimeout(resolve, 0); }, 0);
|
|
165
|
+
});
|
|
166
|
+
}`;
|
|
167
|
+
/** JS snippet defining `__selectOptionValues(node)` — option values of a <select>, [] otherwise. */
|
|
168
|
+
export const SELECT_OPTIONS_SNIPPET = `
|
|
169
|
+
function __selectOptionValues(node) {
|
|
170
|
+
if (!node.options || typeof node.options.length !== 'number') return [];
|
|
171
|
+
return Array.prototype.map.call(node.options, function (o) { return String(o.value); });
|
|
172
|
+
}`;
|
|
173
|
+
/**
|
|
174
|
+
* Shared script body for `type` and `select` value mode. Returns an arrow IIFE
|
|
175
|
+
* that resolves (via a Promise — the bridge awaits it) to a JSON string and
|
|
176
|
+
* never throws into the bridge.
|
|
177
|
+
*
|
|
178
|
+
* Sequence: snapshot `previousValue` → [focus] → [clear + input] → settle →
|
|
179
|
+
* re-resolve the target (the app may have re-mounted it) → snapshot `baseline`
|
|
180
|
+
* → native write → `applied` → refuse values the browser discarded or clamped
|
|
181
|
+
* back → input, change → verify. `reverted` means the re-read equals
|
|
182
|
+
* `baseline` or `previousValue`; comparing only against the pre-command value
|
|
183
|
+
* would let an app that accepted the clear (or changed the value on focus) but
|
|
184
|
+
* rejected the real write pass as `transformed`. A field the app empties after
|
|
185
|
+
* a non-empty write is a failure too ("cleared"): the intended value did not
|
|
186
|
+
* land whether the app rejected or consumed it.
|
|
187
|
+
*/
|
|
188
|
+
export function buildSetValueScript(selector, value, o) {
|
|
189
|
+
const escapedSelector = escapeSelector(selector);
|
|
190
|
+
const safeValue = JSON.stringify(value);
|
|
191
|
+
const tagExpr = o.lowerCaseTagName ? 'el.tagName.toLowerCase()' : 'el.tagName';
|
|
192
|
+
const focusBlock = o.focus
|
|
193
|
+
? `
|
|
194
|
+
if (typeof el.focus === 'function') el.focus();`
|
|
195
|
+
: '';
|
|
196
|
+
const clearBlock = o.clear
|
|
197
|
+
? `
|
|
198
|
+
if (typeof el.select === 'function') el.select();
|
|
199
|
+
writer.set('');
|
|
200
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));`
|
|
201
|
+
: '';
|
|
202
|
+
return `(() => {${NATIVE_VALUE_WRITER_SNIPPET}${VERIFY_POLL_SNIPPET}${SETTLE_SNIPPET}${SELECT_OPTIONS_SNIPPET}
|
|
203
|
+
var selector = '${escapedSelector}';
|
|
204
|
+
var requested = ${safeValue};
|
|
205
|
+
var clearRan = ${o.clear ? 'true' : 'false'};
|
|
206
|
+
var fail = function (extra) { return JSON.stringify(Object.assign({ success: false, selector: selector }, extra)); };
|
|
207
|
+
function describe(node) {
|
|
208
|
+
var t = node.tagName.toLowerCase();
|
|
209
|
+
var it = t === 'input' ? String(node.type || 'text').toLowerCase() : '';
|
|
210
|
+
return '<' + t + (it ? ' type=' + it : '') + '>';
|
|
211
|
+
}
|
|
212
|
+
function unsupported(node) {
|
|
213
|
+
return { error: 'Unsupported element ' + describe(node) + ': no value setter', hint: 'Supported: <input>, <textarea>, <select>, and custom elements that expose a value property. For contenteditable regions use \`eval\`.' };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
var el = document.querySelector(selector);
|
|
217
|
+
if (!el) return fail({ error: ${o.notFoundErrorExpr} });
|
|
218
|
+
var tagName = ${tagExpr};
|
|
219
|
+
var inputType = el.tagName === 'INPUT' ? String(el.type || 'text').toLowerCase() : '';
|
|
220
|
+
if (inputType === 'checkbox' || inputType === 'radio') {
|
|
221
|
+
return fail({ tagName: tagName, error: 'Cannot set a text value on <input type=' + inputType + '>', hint: 'Use \`select <selector> --toggle\` to change its checked state.' });
|
|
222
|
+
}
|
|
223
|
+
if (inputType === 'file') {
|
|
224
|
+
return fail({ tagName: tagName, error: 'Cannot set the value of <input type=file>: browsers reject scripted file values' });
|
|
225
|
+
}
|
|
226
|
+
var writer = __resolveValueWriter(el);
|
|
227
|
+
if (!writer) return fail(Object.assign({ tagName: tagName }, unsupported(el)));
|
|
228
|
+
if (el.tagName === 'SELECT') {
|
|
229
|
+
var options = __selectOptionValues(el);
|
|
230
|
+
if (options.indexOf(requested) === -1) {
|
|
231
|
+
return fail({ tagName: tagName, requestedValue: requested, options: options.slice(0, 50), error: 'No <option> with value ' + JSON.stringify(requested), hint: 'Pass the option value attribute, not its label. Available values: ' + options.slice(0, 20).join(', ') });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
var previousValue = String(el.value);${focusBlock}${clearBlock}
|
|
235
|
+
var base = { selector: selector, tagName: tagName, requestedValue: requested, previousValue: previousValue };
|
|
236
|
+
return __settle().then(function () {
|
|
237
|
+
var live = document.querySelector(selector);
|
|
238
|
+
if (!live) {
|
|
239
|
+
return fail(Object.assign({ error: 'Element left the document after focus/clear (removed or re-rendered without a matching selector); nothing was written', hint: 'The app re-rendered in response to focus or the clear. Re-query the DOM and retry, or target a stable selector.' }, base));
|
|
240
|
+
}
|
|
241
|
+
if (live !== el) {
|
|
242
|
+
el = live;
|
|
243
|
+
writer = __resolveValueWriter(el);
|
|
244
|
+
if (!writer) return fail(Object.assign({}, base, unsupported(el)));
|
|
245
|
+
}
|
|
246
|
+
var baseline = String(el.value);
|
|
247
|
+
writer.set(requested);
|
|
248
|
+
var applied = String(el.value);
|
|
249
|
+
if (writer.kind === 'native' && requested !== '' && applied === '') {
|
|
250
|
+
// Undo our own writes only: after --clear that is the pre-clear value;
|
|
251
|
+
// otherwise the value the field held right before the write (which an
|
|
252
|
+
// app may have changed on focus).
|
|
253
|
+
var restored = clearRan ? previousValue : baseline;
|
|
254
|
+
writer.set(restored);
|
|
255
|
+
if (clearRan) el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
256
|
+
return fail(Object.assign({
|
|
257
|
+
value: restored,
|
|
258
|
+
error: 'Browser discarded the value: ' + describe(el) + ' sanitized ' + JSON.stringify(requested) + ' to ""',
|
|
259
|
+
hint: 'Use a value the control accepts (e.g. digits for type=number, YYYY-MM-DD for type=date). The previous value was restored' + (clearRan ? ' (the field had already been cleared, so an input event was dispatched for the restore).' : ' and no events were dispatched.')
|
|
260
|
+
}, base));
|
|
261
|
+
}
|
|
262
|
+
if (writer.kind === 'native' && requested !== baseline && applied === baseline) {
|
|
263
|
+
return fail(Object.assign({
|
|
264
|
+
value: baseline,
|
|
265
|
+
error: 'Browser did not take the value: ' + describe(el) + ' still reads ' + JSON.stringify(baseline) + ' after writing ' + JSON.stringify(requested),
|
|
266
|
+
hint: 'The control clamped or sanitized the value back onto its current value (e.g. min/max on type=range, the format of type=color). No events were dispatched.'
|
|
267
|
+
}, base));
|
|
268
|
+
}
|
|
269
|
+
// Built-in controls may legitimately normalize the value (newlines stripped, numbers canonicalized): verify against what the setter kept. Custom elements may reflect asynchronously: verify against the request.
|
|
270
|
+
var expected = writer.kind === 'native' ? applied : requested;
|
|
271
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
272
|
+
(document.querySelector(selector) || el).dispatchEvent(new Event('change', { bubbles: true }));
|
|
273
|
+
return __verify(function () {
|
|
274
|
+
var cur = document.querySelector(selector) || (el.isConnected ? el : null);
|
|
275
|
+
return cur ? String(cur.value) : null;
|
|
276
|
+
}, expected, ${o.verifyTimeoutMs}, ${VERIFY_INTERVAL_MS}).then(function (v) {
|
|
277
|
+
if (v.ok) {
|
|
278
|
+
return JSON.stringify(Object.assign({ success: true, value: v.observed, verified: true, verification: 'matched' }, base));
|
|
279
|
+
}
|
|
280
|
+
if (v.observed === null) {
|
|
281
|
+
return fail(Object.assign({ value: applied, verified: false, error: 'Element left the document after the write; the value could not be verified', hint: 'The app removed or re-rendered the element without a matching selector after the input/change events (navigation, a remount). Re-query the DOM to confirm the app state.' }, base));
|
|
282
|
+
}
|
|
283
|
+
var observed = v.observed === undefined ? '' : v.observed;
|
|
284
|
+
if (observed === baseline || observed === previousValue) {
|
|
285
|
+
return fail(Object.assign({
|
|
286
|
+
value: observed, verified: false, verification: 'reverted',
|
|
287
|
+
error: 'Value reverted: wrote ' + JSON.stringify(applied) + ' but the element reads ' + JSON.stringify(observed) + ' again after ${o.verifyTimeoutMs}ms',
|
|
288
|
+
hint: 'The app restored the previous value: a controlled input whose change handler did not accept the write (app-side validation), or one that normalized it to the value already present. Check the app state binding; if the app applies values asynchronously, raise --verify-timeout.'
|
|
289
|
+
}, base));
|
|
290
|
+
}
|
|
291
|
+
if (requested !== '' && observed === '') {
|
|
292
|
+
return fail(Object.assign({
|
|
293
|
+
value: observed, verified: false, verification: 'reverted',
|
|
294
|
+
error: 'Value cleared: wrote ' + JSON.stringify(applied) + ' but the element is empty after ${o.verifyTimeoutMs}ms',
|
|
295
|
+
hint: 'The app emptied the field after the write: it rejected the value (validation reset) or consumed it (e.g. a tag/chip input). Check the app state to tell which.'
|
|
296
|
+
}, base));
|
|
297
|
+
}
|
|
298
|
+
return JSON.stringify(Object.assign({ success: true, value: observed, verified: false, verification: 'transformed' }, base));
|
|
299
|
+
});
|
|
300
|
+
}).catch(function (e) {
|
|
301
|
+
return fail({ error: 'Script error: ' + (e && e.message ? e.message : String(e)) });
|
|
302
|
+
});
|
|
303
|
+
} catch (e) {
|
|
304
|
+
return fail({ error: 'Script error: ' + (e && e.message ? e.message : String(e)) });
|
|
305
|
+
}
|
|
306
|
+
})()`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Parses an interaction result from the bridge. dev_bridge.rs turns a thrown
|
|
310
|
+
* script into the string "ERROR: <message>", which JSON.parse would reject
|
|
311
|
+
* with an opaque SyntaxError — surface it as an actionable error instead.
|
|
312
|
+
*/
|
|
313
|
+
export function parseInteractResult(raw, schema, command) {
|
|
314
|
+
const text = String(raw ?? '');
|
|
315
|
+
if (text.startsWith('ERROR: ')) {
|
|
316
|
+
throw new Error(`${command} failed: the bridge script threw: ${text.slice('ERROR: '.length)}`);
|
|
317
|
+
}
|
|
318
|
+
let parsed;
|
|
319
|
+
try {
|
|
320
|
+
parsed = JSON.parse(text);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
throw new Error(`${command} failed: bridge returned a non-JSON result: ${text.slice(0, 200)}`);
|
|
324
|
+
}
|
|
325
|
+
return schema.parse(parsed);
|
|
326
|
+
}
|
|
62
327
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/commands/interact/shared.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/commands/interact/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import type { VerifyOptions } from './shared.js';
|
|
2
3
|
/**
|
|
3
|
-
* Build a JS IIFE
|
|
4
|
+
* Build a JS IIFE that types text into the element matched by selector.
|
|
5
|
+
*
|
|
6
|
+
* Both the --clear write and the final write go through the element's native
|
|
7
|
+
* prototype value setter (see NATIVE_VALUE_WRITER_SNIPPET) so React-style
|
|
8
|
+
* instance trackers see the following input/change events as real changes
|
|
9
|
+
* (#10). The script then re-reads the value (synchronously, then polled up to
|
|
10
|
+
* verifyTimeoutMs) and reports `reverted` when the app restored the previous
|
|
11
|
+
* value. Returns a Promise-resolving IIFE; the bridge awaits it.
|
|
4
12
|
*/
|
|
5
|
-
export declare function buildTypeScript(selector: string, text: string, clear: boolean): string;
|
|
13
|
+
export declare function buildTypeScript(selector: string, text: string, clear: boolean, options?: VerifyOptions): string;
|
|
6
14
|
export declare function registerType(program: Command): void;
|
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { resolveBridge } from '../shared.js';
|
|
3
|
-
import { addInteractOptions,
|
|
3
|
+
import { addInteractOptions, addVerifyOptions, buildSetValueScript, parseInteractResult, resolveVerifyTimeout, } from './shared.js';
|
|
4
4
|
import { TypeResultSchema } from '../../schemas/interact.js';
|
|
5
5
|
/**
|
|
6
|
-
* Build a JS IIFE
|
|
6
|
+
* Build a JS IIFE that types text into the element matched by selector.
|
|
7
|
+
*
|
|
8
|
+
* Both the --clear write and the final write go through the element's native
|
|
9
|
+
* prototype value setter (see NATIVE_VALUE_WRITER_SNIPPET) so React-style
|
|
10
|
+
* instance trackers see the following input/change events as real changes
|
|
11
|
+
* (#10). The script then re-reads the value (synchronously, then polled up to
|
|
12
|
+
* verifyTimeoutMs) and reports `reverted` when the app restored the previous
|
|
13
|
+
* value. Returns a Promise-resolving IIFE; the bridge awaits it.
|
|
7
14
|
*/
|
|
8
|
-
export function buildTypeScript(selector, text, clear) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));`
|
|
17
|
-
: '';
|
|
18
|
-
return `(() => {
|
|
19
|
-
var el = document.querySelector('${escapedSelector}');
|
|
20
|
-
if (!el) {
|
|
21
|
-
return JSON.stringify({ success: false, selector: '${escapedSelector}', error: 'Element not found' });
|
|
22
|
-
}
|
|
23
|
-
el.focus();${clearBlock}
|
|
24
|
-
el.value = ${safeText};
|
|
25
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
26
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
27
|
-
return JSON.stringify({ success: true, selector: '${escapedSelector}', tagName: el.tagName, value: el.value });
|
|
28
|
-
})()`;
|
|
15
|
+
export function buildTypeScript(selector, text, clear, options) {
|
|
16
|
+
return buildSetValueScript(selector, text, {
|
|
17
|
+
focus: true,
|
|
18
|
+
clear,
|
|
19
|
+
lowerCaseTagName: false,
|
|
20
|
+
notFoundErrorExpr: `'Element not found'`,
|
|
21
|
+
verifyTimeoutMs: resolveVerifyTimeout(options),
|
|
22
|
+
});
|
|
29
23
|
}
|
|
30
24
|
export function registerType(program) {
|
|
31
25
|
const cmd = new Command('type')
|
|
@@ -37,22 +31,43 @@ export function registerType(program) {
|
|
|
37
31
|
Examples:
|
|
38
32
|
$ tauri-agent-tools type "#username" "admin"
|
|
39
33
|
$ tauri-agent-tools type "input[name=email]" "user@example.com" --clear
|
|
40
|
-
$ tauri-agent-tools type ".search-input" "hello world" --json
|
|
34
|
+
$ tauri-agent-tools type ".search-input" "hello world" --json
|
|
35
|
+
$ tauri-agent-tools type "#q" "term" --verify-timeout 2000 # app applies the value asynchronously
|
|
36
|
+
|
|
37
|
+
The value is written through the element's native prototype setter followed by
|
|
38
|
+
bubbling input + change events, so React/Vue/Svelte controlled inputs see it.
|
|
39
|
+
The element is then re-read; the command fails with "Value reverted" when the
|
|
40
|
+
app restored the previous value (a controlled input that rejected the write).`);
|
|
41
41
|
addInteractOptions(cmd);
|
|
42
|
+
addVerifyOptions(cmd);
|
|
42
43
|
cmd.action(async (selector, text, opts) => {
|
|
44
|
+
// Build first: an invalid --verify-timeout fails before any bridge call.
|
|
45
|
+
const script = buildTypeScript(selector, text, !!opts.clear, { verifyTimeoutMs: opts.verifyTimeout });
|
|
43
46
|
const bridge = await resolveBridge(opts);
|
|
44
|
-
const script = buildTypeScript(selector, text, !!opts.clear);
|
|
45
47
|
const raw = await bridge.eval(script);
|
|
46
|
-
const result =
|
|
48
|
+
const result = parseInteractResult(raw, TypeResultSchema, 'Type');
|
|
47
49
|
if (!result.success) {
|
|
48
|
-
|
|
50
|
+
// With --json the failure object (verification, hint, options, …) goes to
|
|
51
|
+
// stdout so scripts can branch on it; the thrown error still reaches
|
|
52
|
+
// stderr and sets exit code 1.
|
|
53
|
+
if (opts.json)
|
|
54
|
+
console.log(JSON.stringify(result, null, 2));
|
|
55
|
+
const hint = result.hint ? `\n hint: ${result.hint}` : '';
|
|
56
|
+
throw new Error(`Type failed: ${result.error} (selector: ${result.selector})${hint}`);
|
|
49
57
|
}
|
|
50
58
|
if (opts.json) {
|
|
51
59
|
console.log(JSON.stringify(result, null, 2));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const tag = (result.tagName ?? 'element').toLowerCase();
|
|
63
|
+
let note = '';
|
|
64
|
+
if (result.verification === 'transformed') {
|
|
65
|
+
note = ` (app transformed the requested value ${JSON.stringify(result.requestedValue)})`;
|
|
52
66
|
}
|
|
53
|
-
else {
|
|
54
|
-
|
|
67
|
+
else if (result.requestedValue !== undefined && result.value !== result.requestedValue) {
|
|
68
|
+
note = ` (browser normalized the requested value ${JSON.stringify(result.requestedValue)})`;
|
|
55
69
|
}
|
|
70
|
+
console.log(`Typed into ${tag}: "${result.value}"${note}`);
|
|
56
71
|
});
|
|
57
72
|
program.addCommand(cmd);
|
|
58
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../../src/commands/interact/type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,
|
|
1
|
+
{"version":3,"file":"type.js","sourceRoot":"","sources":["../../../src/commands/interact/type.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,25 +37,56 @@ export declare const ClickResultSchema: z.ZodObject<{
|
|
|
37
37
|
tagName?: string | undefined;
|
|
38
38
|
}>;
|
|
39
39
|
export type ClickResult = z.infer<typeof ClickResultSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* Outcome of the post-write re-read performed by `type` and `select`:
|
|
42
|
+
* - `matched`: the element still reports the value the setter applied.
|
|
43
|
+
* - `transformed`: the app replaced it with a different value (mask, formatter) — a soft success.
|
|
44
|
+
* - `reverted`: the element reads its pre-write value again — the app rejected the write (failure).
|
|
45
|
+
*/
|
|
46
|
+
export declare const WriteVerificationSchema: z.ZodEnum<["matched", "transformed", "reverted"]>;
|
|
47
|
+
export type WriteVerification = z.infer<typeof WriteVerificationSchema>;
|
|
40
48
|
export declare const TypeResultSchema: z.ZodObject<{
|
|
41
49
|
success: z.ZodBoolean;
|
|
42
50
|
selector: z.ZodOptional<z.ZodString>;
|
|
43
51
|
tagName: z.ZodOptional<z.ZodString>;
|
|
44
52
|
error: z.ZodOptional<z.ZodString>;
|
|
45
53
|
} & {
|
|
54
|
+
/** The value as requested on the command line. */
|
|
55
|
+
requestedValue: z.ZodOptional<z.ZodString>;
|
|
56
|
+
/** `el.value` before the write. */
|
|
57
|
+
previousValue: z.ZodOptional<z.ZodString>;
|
|
58
|
+
/** True when the re-read equals the value the setter applied. */
|
|
59
|
+
verified: z.ZodOptional<z.ZodBoolean>;
|
|
60
|
+
verification: z.ZodOptional<z.ZodEnum<["matched", "transformed", "reverted"]>>;
|
|
61
|
+
/** Available `<option>` values when a `<select>` had no match. */
|
|
62
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
63
|
+
/** Actionable guidance accompanying a failure. */
|
|
64
|
+
hint: z.ZodOptional<z.ZodString>;
|
|
46
65
|
value: z.ZodOptional<z.ZodString>;
|
|
47
66
|
}, "strip", z.ZodTypeAny, {
|
|
48
67
|
success: boolean;
|
|
49
68
|
value?: string | undefined;
|
|
69
|
+
options?: string[] | undefined;
|
|
50
70
|
error?: string | undefined;
|
|
51
71
|
selector?: string | undefined;
|
|
72
|
+
hint?: string | undefined;
|
|
52
73
|
tagName?: string | undefined;
|
|
74
|
+
requestedValue?: string | undefined;
|
|
75
|
+
previousValue?: string | undefined;
|
|
76
|
+
verified?: boolean | undefined;
|
|
77
|
+
verification?: "matched" | "transformed" | "reverted" | undefined;
|
|
53
78
|
}, {
|
|
54
79
|
success: boolean;
|
|
55
80
|
value?: string | undefined;
|
|
81
|
+
options?: string[] | undefined;
|
|
56
82
|
error?: string | undefined;
|
|
57
83
|
selector?: string | undefined;
|
|
84
|
+
hint?: string | undefined;
|
|
58
85
|
tagName?: string | undefined;
|
|
86
|
+
requestedValue?: string | undefined;
|
|
87
|
+
previousValue?: string | undefined;
|
|
88
|
+
verified?: boolean | undefined;
|
|
89
|
+
verification?: "matched" | "transformed" | "reverted" | undefined;
|
|
59
90
|
}>;
|
|
60
91
|
export type TypeResult = z.infer<typeof TypeResultSchema>;
|
|
61
92
|
export declare const ScrollResultSchema: z.ZodObject<{
|
|
@@ -81,22 +112,49 @@ export declare const SelectResultSchema: z.ZodObject<{
|
|
|
81
112
|
tagName: z.ZodOptional<z.ZodString>;
|
|
82
113
|
error: z.ZodOptional<z.ZodString>;
|
|
83
114
|
} & {
|
|
115
|
+
/** The value as requested on the command line. */
|
|
116
|
+
requestedValue: z.ZodOptional<z.ZodString>;
|
|
117
|
+
/** `el.value` before the write. */
|
|
118
|
+
previousValue: z.ZodOptional<z.ZodString>;
|
|
119
|
+
/** True when the re-read equals the value the setter applied. */
|
|
120
|
+
verified: z.ZodOptional<z.ZodBoolean>;
|
|
121
|
+
verification: z.ZodOptional<z.ZodEnum<["matched", "transformed", "reverted"]>>;
|
|
122
|
+
/** Available `<option>` values when a `<select>` had no match. */
|
|
123
|
+
options: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
124
|
+
/** Actionable guidance accompanying a failure. */
|
|
125
|
+
hint: z.ZodOptional<z.ZodString>;
|
|
84
126
|
value: z.ZodOptional<z.ZodString>;
|
|
85
127
|
checked: z.ZodOptional<z.ZodBoolean>;
|
|
128
|
+
/** `checked` before a `--toggle`. */
|
|
129
|
+
previousChecked: z.ZodOptional<z.ZodBoolean>;
|
|
86
130
|
}, "strip", z.ZodTypeAny, {
|
|
87
131
|
success: boolean;
|
|
88
132
|
value?: string | undefined;
|
|
133
|
+
options?: string[] | undefined;
|
|
89
134
|
error?: string | undefined;
|
|
90
135
|
selector?: string | undefined;
|
|
136
|
+
hint?: string | undefined;
|
|
91
137
|
tagName?: string | undefined;
|
|
138
|
+
requestedValue?: string | undefined;
|
|
139
|
+
previousValue?: string | undefined;
|
|
140
|
+
verified?: boolean | undefined;
|
|
141
|
+
verification?: "matched" | "transformed" | "reverted" | undefined;
|
|
92
142
|
checked?: boolean | undefined;
|
|
143
|
+
previousChecked?: boolean | undefined;
|
|
93
144
|
}, {
|
|
94
145
|
success: boolean;
|
|
95
146
|
value?: string | undefined;
|
|
147
|
+
options?: string[] | undefined;
|
|
96
148
|
error?: string | undefined;
|
|
97
149
|
selector?: string | undefined;
|
|
150
|
+
hint?: string | undefined;
|
|
98
151
|
tagName?: string | undefined;
|
|
152
|
+
requestedValue?: string | undefined;
|
|
153
|
+
previousValue?: string | undefined;
|
|
154
|
+
verified?: boolean | undefined;
|
|
155
|
+
verification?: "matched" | "transformed" | "reverted" | undefined;
|
|
99
156
|
checked?: boolean | undefined;
|
|
157
|
+
previousChecked?: boolean | undefined;
|
|
100
158
|
}>;
|
|
101
159
|
export type SelectResult = z.infer<typeof SelectResultSchema>;
|
|
102
160
|
export declare const InvokeResultSchema: z.ZodObject<{
|
package/dist/schemas/interact.js
CHANGED
|
@@ -9,8 +9,30 @@ export const InteractionResultSchema = z.object({
|
|
|
9
9
|
export const ClickResultSchema = InteractionResultSchema.extend({
|
|
10
10
|
text: z.string().optional(),
|
|
11
11
|
});
|
|
12
|
+
/**
|
|
13
|
+
* Outcome of the post-write re-read performed by `type` and `select`:
|
|
14
|
+
* - `matched`: the element still reports the value the setter applied.
|
|
15
|
+
* - `transformed`: the app replaced it with a different value (mask, formatter) — a soft success.
|
|
16
|
+
* - `reverted`: the element reads its pre-write value again — the app rejected the write (failure).
|
|
17
|
+
*/
|
|
18
|
+
export const WriteVerificationSchema = z.enum(['matched', 'transformed', 'reverted']);
|
|
19
|
+
/** Optional-only fields shared by the value-writing commands. */
|
|
20
|
+
const WriteResultFields = {
|
|
21
|
+
/** The value as requested on the command line. */
|
|
22
|
+
requestedValue: z.string().optional(),
|
|
23
|
+
/** `el.value` before the write. */
|
|
24
|
+
previousValue: z.string().optional(),
|
|
25
|
+
/** True when the re-read equals the value the setter applied. */
|
|
26
|
+
verified: z.boolean().optional(),
|
|
27
|
+
verification: WriteVerificationSchema.optional(),
|
|
28
|
+
/** Available `<option>` values when a `<select>` had no match. */
|
|
29
|
+
options: z.array(z.string()).optional(),
|
|
30
|
+
/** Actionable guidance accompanying a failure. */
|
|
31
|
+
hint: z.string().optional(),
|
|
32
|
+
};
|
|
12
33
|
export const TypeResultSchema = InteractionResultSchema.extend({
|
|
13
34
|
value: z.string().optional(),
|
|
35
|
+
...WriteResultFields,
|
|
14
36
|
});
|
|
15
37
|
export const ScrollResultSchema = z.object({
|
|
16
38
|
success: z.boolean(),
|
|
@@ -21,6 +43,9 @@ export const ScrollResultSchema = z.object({
|
|
|
21
43
|
export const SelectResultSchema = InteractionResultSchema.extend({
|
|
22
44
|
value: z.string().optional(),
|
|
23
45
|
checked: z.boolean().optional(),
|
|
46
|
+
/** `checked` before a `--toggle`. */
|
|
47
|
+
previousChecked: z.boolean().optional(),
|
|
48
|
+
...WriteResultFields,
|
|
24
49
|
});
|
|
25
50
|
export const InvokeResultSchema = z.object({
|
|
26
51
|
success: z.boolean(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interact.js","sourceRoot":"","sources":["../../src/schemas/interact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8BAA8B;AAE9B,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,MAAM,CAAC;IAC9D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,MAAM,CAAC;IAC7D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"interact.js","sourceRoot":"","sources":["../../src/schemas/interact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8BAA8B;AAE9B,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,MAAM,CAAC;IAC9D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;AAGtF,iEAAiE;AACjE,MAAM,iBAAiB,GAAG;IACxB,kDAAkD;IAClD,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,mCAAmC;IACnC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,iEAAiE;IACjE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,YAAY,EAAE,uBAAuB,CAAC,QAAQ,EAAE;IAChD,kEAAkE;IAClE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvC,kDAAkD;IAClD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,MAAM,CAAC;IAC7D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,GAAG,iBAAiB;CACrB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC,MAAM,CAAC;IAC/D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,qCAAqC;IACrC,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACvC,GAAG,iBAAiB;CACrB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tauri-agent-tools",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Agent-driven inspection toolkit for Tauri desktop apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"@vitest/coverage-v8": "^3.2.4",
|
|
57
57
|
"eslint": "^9.0.0",
|
|
58
|
+
"jsdom": "^29.1.1",
|
|
59
|
+
"react": "^19.2.8",
|
|
60
|
+
"react-dom": "^19.2.8",
|
|
58
61
|
"typescript": "^5.8.0",
|
|
59
62
|
"typescript-eslint": "^8.0.0",
|
|
60
63
|
"vitest": "^3.1.0"
|