@webhands/core 0.1.0 → 0.3.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.
Files changed (55) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +177 -0
  3. package/dist/cookies-export.d.ts +5 -5
  4. package/dist/cookies-export.d.ts.map +1 -1
  5. package/dist/cookies-export.js +4 -4
  6. package/dist/errors.d.ts +24 -1
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +24 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/hand-host.d.ts +217 -0
  11. package/dist/hand-host.d.ts.map +1 -0
  12. package/dist/hand-host.js +351 -0
  13. package/dist/hand-host.js.map +1 -0
  14. package/dist/hand-loading.d.ts +128 -0
  15. package/dist/hand-loading.d.ts.map +1 -0
  16. package/dist/hand-loading.js +143 -0
  17. package/dist/hand-loading.js.map +1 -0
  18. package/dist/index.d.ts +6 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/playwright-attach-transport.d.ts +9 -0
  23. package/dist/playwright-attach-transport.d.ts.map +1 -1
  24. package/dist/playwright-attach-transport.js +53 -91
  25. package/dist/playwright-attach-transport.js.map +1 -1
  26. package/dist/playwright-launch-transport.d.ts +81 -62
  27. package/dist/playwright-launch-transport.d.ts.map +1 -1
  28. package/dist/playwright-launch-transport.js +143 -210
  29. package/dist/playwright-launch-transport.js.map +1 -1
  30. package/dist/remote-session.d.ts +12 -2
  31. package/dist/remote-session.d.ts.map +1 -1
  32. package/dist/remote-session.js +37 -6
  33. package/dist/remote-session.js.map +1 -1
  34. package/dist/seam.d.ts +13 -5
  35. package/dist/seam.d.ts.map +1 -1
  36. package/dist/session-rpc.d.ts +76 -12
  37. package/dist/session-rpc.d.ts.map +1 -1
  38. package/dist/session-rpc.js +76 -8
  39. package/dist/session-rpc.js.map +1 -1
  40. package/dist/stub-transport.d.ts +2 -2
  41. package/dist/stub-transport.d.ts.map +1 -1
  42. package/dist/stub-transport.js +11 -0
  43. package/dist/stub-transport.js.map +1 -1
  44. package/package.json +24 -2
  45. package/src/cookies-export.ts +5 -5
  46. package/src/errors.ts +31 -0
  47. package/src/hand-host.ts +511 -0
  48. package/src/hand-loading.ts +254 -0
  49. package/src/index.ts +24 -2
  50. package/src/playwright-attach-transport.ts +65 -119
  51. package/src/playwright-launch-transport.ts +235 -249
  52. package/src/remote-session.ts +43 -5
  53. package/src/seam.ts +13 -5
  54. package/src/session-rpc.ts +121 -11
  55. package/src/stub-transport.ts +15 -3
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # webhands
2
+
3
+ A CLI (built with [`incur`](https://github.com/wevm/incur), so it doubles as an
4
+ MCP server) that drives a real, persistent browser via Playwright, letting an
5
+ agent or human control any website from a genuinely logged-in browser session on
6
+ their own machine and IP.
7
+
8
+ It launches (or attaches to) a Chromium browser using a dedicated profile,
9
+ supports a one-time headed login that is later reused headless, keeps the session
10
+ alive across separate CLI invocations behind a long-lived `serve` process, and
11
+ exposes page verbs (`goto`, `snapshot`, `click`, `type`, `eval`, `wait`,
12
+ `cookies`) with structured output.
13
+
14
+ ## Use it via your AI agent (start here)
15
+
16
+ The simplest way to use `webhands` is to let your coding agent (Claude Code,
17
+ Cursor, etc.) run it through plain `bash` with `npx`. No MCP wiring, no install
18
+ step — the agent just runs `npx webhands <verb>` commands. The first run of
19
+ `npx webhands` fetches the package automatically.
20
+
21
+ Give your agent something like: *"Use `webhands` to open Kayak and read me the
22
+ live prices for EDI→BOM on 31 Oct."* A capable agent will then:
23
+
24
+ ```sh
25
+ # 1. start & HOLD the browser. serve blocks, so the agent backgrounds it:
26
+ nohup npx webhands serve --headed > /tmp/webhands.log 2>&1 &
27
+ sleep 12 && cat /tmp/webhands.log # confirm it printed an endpoint + pid
28
+
29
+ # 2. navigate the live page (separate invocation, same browser):
30
+ npx webhands goto 'https://www.kayak.co.uk/flights/EDI-BOM/2026-10-31?sort=price_a'
31
+
32
+ # 3. let JS results render, then read the page token-cheaply:
33
+ npx webhands wait --ms 8000
34
+ npx webhands snapshot --token-limit 6000
35
+
36
+ # 4. always tear down when done:
37
+ npx webhands stop
38
+ ```
39
+
40
+ Three things a new user should know up front:
41
+
42
+ - **You log in once, in a window you can see.** Run `npx webhands setup-profile`
43
+ (or start with `serve --headed`) and sign in / clear any cookie or anti-bot
44
+ prompt yourself. That state is saved to a dedicated profile and reused on later
45
+ runs. The tool never bypasses logins or solves CAPTCHAs — you do that part.
46
+ - **It acts as the real, logged-in you.** Reading pages is low-risk; let the agent
47
+ do that freely. But anything that spends money, books, posts, or changes account
48
+ state should be YOUR explicit decision — have the agent surface the link and let
49
+ you finish checkout. (See *Scope and honesty* below.)
50
+ - **Anti-bot sites may need the visible window.** Headless runs can hit a
51
+ "you look like a bot" page on sites like Kayak. The fix is to run `--headed` and
52
+ clear the challenge yourself once, not to defeat it.
53
+
54
+ For the full agent playbook (workflow, gotchas, guardrails) install the bundled
55
+ skill: `npx webhands skills add` then look for `use-webhands`. Per-verb flag
56
+ reference: `npx webhands <verb> --help` or `npx webhands --llms-full`.
57
+
58
+ ## How it works (the pipe)
59
+
60
+ The browser is owned by ONE long-lived `serve` process; each verb invocation is a
61
+ thin client that drives the SAME live page and exits (see
62
+ [`docs/adr/0005`](docs/adr/0005-incur-serve-hosts-the-long-lived-session.md)). The
63
+ typical end-to-end flow:
64
+
65
+ 1. `webhands setup-profile`: opens the dedicated profile in a
66
+ VISIBLE browser so you log in / clear any anti-bot challenge ONCE. State
67
+ (cookies, login, challenge clearance) persists on disk.
68
+ 2. `webhands serve --headless`: launches the one browser against
69
+ that saved profile and keeps it alive (runs until `stop` or Ctrl-C).
70
+ 3. `webhands goto <url>` then `webhands snapshot` (and
71
+ `click` / `type` / `eval` / `wait`): separate invocations that all drive the
72
+ single live page the server holds.
73
+ 4. `webhands stop`: tears the session down.
74
+
75
+ A verb run with no live server prints a clear error telling you to run `serve`
76
+ first; the tool never silently spawns a browser.
77
+
78
+ ## Scope and honesty (please read)
79
+
80
+ This is a **personal-use** tool. Its whole premise is that you drive a browser
81
+ **you logged into yourself**, on **your own machine and your own IP**, reusing
82
+ **your own authenticated session** (see
83
+ [`docs/adr/0002`](docs/adr/0002-real-session-over-fingerprint-spoofing.md)). It is
84
+ deliberately local and single-session by design.
85
+
86
+ - **No login-bypass, no CAPTCHA-solving.** The human does the one-time login and
87
+ clears any anti-bot challenge in the headed `setup-profile` step. This tool
88
+ does NOT bypass authentication or solve CAPTCHAs programmatically, and it is not
89
+ intended to.
90
+ - **No fingerprint-spoofing / anti-detect tricks.** It leans on being a *real*
91
+ browser/profile/IP rather than spoofing. There is no proxy rotation or
92
+ anti-detect build here.
93
+ - **Your own session only.** A replayed/stolen cookie does not work anyway
94
+ (clearance is bound to the browser fingerprint and IP, not just the cookie);
95
+ the design assumes the session is genuinely yours.
96
+
97
+ In short: this is for reading and acting on web apps **you already have an account
98
+ on**, from **your own browser**, the way you could by hand.
99
+
100
+ ## Optional: stealth launch (opt-in, default OFF)
101
+
102
+ Standard Playwright drives Chromium over CDP and calls `Runtime.enable` at
103
+ startup. That emits a side-effect a few lines of page JS can detect, and some
104
+ anti-bot WAFs (Imperva/Cloudflare/DataDome) use it to serve an "Access Denied"
105
+ block page *before the page even renders* — even on a real residential IP, even
106
+ headed. `@webhands/core` can optionally launch via
107
+ [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright) (an
108
+ API-compatible Playwright fork that patches exactly these CDP leaks) to remove
109
+ that one tell.
110
+
111
+ This is **off by default** — vanilla Playwright stays the default. To enable it:
112
+
113
+ 1. Install the optional dependency (it is NOT pulled in unless you ask for it):
114
+
115
+ ```sh
116
+ pnpm add patchright
117
+ # if you do NOT pass --use-system-browser chrome, also fetch its browser:
118
+ # pnpm exec patchright install chromium
119
+ ```
120
+
121
+ 2. Bring the session up with `--stealth`. The realistic recipe also drives your
122
+ installed system browser (`--use-system-browser chrome`), headed, against a
123
+ **warmed, logged-in profile**:
124
+
125
+ ```sh
126
+ # serve consumes these (it is where the browser is launched, ADR-0005):
127
+ npx webhands serve --headed --stealth --use-system-browser chrome
128
+ ```
129
+
130
+ `--use-system-browser` is independent of `--stealth`: you can drive real
131
+ Chrome with or without the Patchright path, and stealth with or without a
132
+ system browser. Other channel names work too (e.g. `msedge`).
133
+
134
+ Programmatic equivalent (the `--stealth` / `--use-system-browser` flags map onto
135
+ these transport options):
136
+
137
+ ```ts
138
+ import {PlaywrightLaunchTransport} from '@webhands/core';
139
+
140
+ const transport = new PlaywrightLaunchTransport(
141
+ {}, // profile location (omit for ~/.webhands)
142
+ [], // extra hands
143
+ {stealth: true, systemBrowser: 'chrome'},
144
+ );
145
+ // Stealth + headed + a real logged-in profile is the strongest recipe:
146
+ const session = await transport.open({
147
+ mode: 'launch',
148
+ profile: 'default',
149
+ headed: true,
150
+ });
151
+ ```
152
+
153
+ If stealth is enabled but `patchright` is not installed, the open throws a typed
154
+ `MissingStealthDependencyError` (the CLI prints `pnpm add patchright` as the fix).
155
+ It **never silently falls back** to vanilla Playwright, because that would put
156
+ the tell back without telling you.
157
+
158
+ **Honest caveat.** Stealth addresses ONLY the CDP `Runtime.enable` automation
159
+ tell. It is **necessary-but-not-sufficient**: IP reputation and session/profile
160
+ reputation still matter. The realistic recipe is stealth +
161
+ `systemBrowser: 'chrome'` + headed + a warmed, logged-in profile + a residential
162
+ IP (see
163
+ [`docs/adr/0002`](docs/adr/0002-real-session-over-fingerprint-spoofing.md)).
164
+
165
+ ## Security note (the `serve` endpoint runs arbitrary code)
166
+
167
+ The page verbs execute caller-supplied expressions: `eval` runs a JS expression
168
+ in the page, and a `click`/`type` locator is a raw Playwright locator EXPRESSION
169
+ the controller evaluates (see
170
+ [`docs/adr/0004`](docs/adr/0004-verb-surface-exposes-playwright-locator-semantics.md)).
171
+ That is by design for a LOCAL tool driven by its own agent against your own
172
+ session, but it means the running `serve` endpoint is a code-execution surface.
173
+
174
+ - **Do NOT expose the `serve` endpoint to untrusted callers.** Keep it bound to
175
+ localhost (the default); never bind it to a public interface or hand its URL to
176
+ code you do not trust. Anyone who can call it can run arbitrary JavaScript in
177
+ your logged-in session.
@@ -2,8 +2,8 @@
2
2
  * The `cookies export` / `cookies import` verb's FILE FORMAT (PRD story 11).
3
3
  *
4
4
  * The seam already carries the transport-neutral cookie primitives:
5
- * {@link Page.cookies} reads the active context's cookies and
6
- * {@link Page.setCookies} loads cookies into it. The export/import VERB is built
5
+ * {@link WebHandsPage.cookies} reads the active context's cookies and
6
+ * {@link WebHandsPage.setCookies} loads cookies into it. The export/import VERB is built
7
7
  * ON TOP of those two methods (the forward-note: refine the existing seam,
8
8
  * do NOT add a parallel cookie path). What this module adds is only the
9
9
  * SERIALIZATION the verb needs to move a session to/from disk: how a
@@ -31,11 +31,11 @@ export declare const COOKIES_EXPORT_VERSION: 1;
31
31
  export interface CookiesExport {
32
32
  /** Format version (see {@link COOKIES_EXPORT_VERSION}). */
33
33
  readonly version: typeof COOKIES_EXPORT_VERSION;
34
- /** The exported cookies, exactly as the seam's {@link Page.cookies} returns them. */
34
+ /** The exported cookies, exactly as the seam's {@link WebHandsPage.cookies} returns them. */
35
35
  readonly cookies: readonly Cookie[];
36
36
  }
37
37
  /**
38
- * Serialize the cookies read from the seam ({@link Page.cookies}) into the
38
+ * Serialize the cookies read from the seam ({@link WebHandsPage.cookies}) into the
39
39
  * export file's text. Pretty-printed JSON so a human can read/diff a backed-up
40
40
  * session. This is pure: it does NO disk I/O, so the caller (the CLI verb, a
41
41
  * test) owns WHERE the file lands — which is what lets a test keep its export
@@ -44,7 +44,7 @@ export interface CookiesExport {
44
44
  export declare function serializeCookies(cookies: readonly Cookie[]): string;
45
45
  /**
46
46
  * Parse an export file's text back into the cookies to hand to the seam's
47
- * {@link Page.setCookies} ({@link parse} is pure; the caller does the disk read
47
+ * {@link WebHandsPage.setCookies} ({@link parse} is pure; the caller does the disk read
48
48
  * and the `setCookies` call). Rejects anything that is not a recognised export
49
49
  * envelope so a corrupt or wrong-version file surfaces as a clear error rather
50
50
  * than silently importing nothing or a half-parsed list.
@@ -1 +1 @@
1
- {"version":3,"file":"cookies-export.d.ts","sourceRoot":"","sources":["../src/cookies-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,sBAAsB,CAAC;IAChD,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAyBlE"}
1
+ {"version":3,"file":"cookies-export.d.ts","sourceRoot":"","sources":["../src/cookies-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,OAAO,sBAAsB,CAAC;IAChD,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAyBlE"}
@@ -2,8 +2,8 @@
2
2
  * The `cookies export` / `cookies import` verb's FILE FORMAT (PRD story 11).
3
3
  *
4
4
  * The seam already carries the transport-neutral cookie primitives:
5
- * {@link Page.cookies} reads the active context's cookies and
6
- * {@link Page.setCookies} loads cookies into it. The export/import VERB is built
5
+ * {@link WebHandsPage.cookies} reads the active context's cookies and
6
+ * {@link WebHandsPage.setCookies} loads cookies into it. The export/import VERB is built
7
7
  * ON TOP of those two methods (the forward-note: refine the existing seam,
8
8
  * do NOT add a parallel cookie path). What this module adds is only the
9
9
  * SERIALIZATION the verb needs to move a session to/from disk: how a
@@ -23,7 +23,7 @@
23
23
  */
24
24
  export const COOKIES_EXPORT_VERSION = 1;
25
25
  /**
26
- * Serialize the cookies read from the seam ({@link Page.cookies}) into the
26
+ * Serialize the cookies read from the seam ({@link WebHandsPage.cookies}) into the
27
27
  * export file's text. Pretty-printed JSON so a human can read/diff a backed-up
28
28
  * session. This is pure: it does NO disk I/O, so the caller (the CLI verb, a
29
29
  * test) owns WHERE the file lands — which is what lets a test keep its export
@@ -38,7 +38,7 @@ export function serializeCookies(cookies) {
38
38
  }
39
39
  /**
40
40
  * Parse an export file's text back into the cookies to hand to the seam's
41
- * {@link Page.setCookies} ({@link parse} is pure; the caller does the disk read
41
+ * {@link WebHandsPage.setCookies} ({@link parse} is pure; the caller does the disk read
42
42
  * and the `setCookies` call). Rejects anything that is not a recognised export
43
43
  * envelope so a corrupt or wrong-version file surfaces as a clear error rather
44
44
  * than silently importing nothing or a half-parsed list.
package/dist/errors.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * has to re-derive paths.
16
16
  */
17
17
  /** The closed set of identifiable `core` error conditions. */
18
- export type ControllerErrorCode = 'missing-browser-binary' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active';
18
+ export type ControllerErrorCode = 'missing-browser-binary' | 'missing-stealth-dependency' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active';
19
19
  /**
20
20
  * Base class for every identifiable `core` error. Branch on {@link code}.
21
21
  *
@@ -45,6 +45,29 @@ export declare class MissingBrowserBinaryError extends ControllerError {
45
45
  cause?: unknown;
46
46
  });
47
47
  }
48
+ /**
49
+ * Stealth launch was REQUESTED (the opt-in is on) but the optional `patchright`
50
+ * dependency is not installed/importable. Patchright is an OPTIONAL dependency
51
+ * of `@webhands/core` imported lazily only when stealth is enabled, so a user
52
+ * who never opts in is not forced to install it (ADR-0002: stealth is one extra
53
+ * layer, not the default). When it IS opted into and missing, we refuse LOUDLY
54
+ * with this typed condition rather than silently falling back to vanilla
55
+ * Playwright, because a silent fallback would re-introduce the exact CDP
56
+ * automation tell the user asked us to remove WITHOUT telling them.
57
+ *
58
+ * Mirrors {@link MissingBrowserBinaryError}: a stable typed error whose brittle
59
+ * detection (the dynamic-import failure) is confined to one spot in the launch
60
+ * transport. The CLI can render the exact `pnpm add patchright` fix command by
61
+ * branching on {@link code}.
62
+ */
63
+ export declare class MissingStealthDependencyError extends ControllerError {
64
+ readonly code = "missing-stealth-dependency";
65
+ /** The optional package that must be installed to use stealth. */
66
+ readonly dependency: string;
67
+ constructor(dependency?: string, message?: string, options?: {
68
+ cause?: unknown;
69
+ });
70
+ }
48
71
  /**
49
72
  * The named profile has not been set up yet: its dedicated profile directory
50
73
  * does not exist on disk. A profile is created by the headed `setup-profile`
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAC5B,wBAAwB,GACxB,iBAAiB,GACjB,qBAAqB,GACrB,mBAAmB,GACnB,gBAAgB,GAChB,wBAAwB,CAAC;AAE5B;;;;;;GAMG;AACH,8BAAsB,eAAgB,SAAQ,KAAK;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAG,IAAI,CAAU;gBAE/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAMxD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAA0D,EACnE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,MAA0F,EACnG,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAM5B;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,IAAI,yBAAyB;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAuJ,EAChK,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACxD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGzB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAA+H,EACxI,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,oBAAoB;gBAGhC,OAAO,GAAE,MAAoF,EAC7F,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;gBAGxC,OAAO,GAAE,MAAmE,EAC5E,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAC5B,wBAAwB,GACxB,4BAA4B,GAC5B,iBAAiB,GACjB,qBAAqB,GACrB,mBAAmB,GACnB,gBAAgB,GAChB,wBAAwB,CAAC;AAE5B;;;;;;GAMG;AACH,8BAAsB,eAAgB,SAAQ,KAAK;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAG,IAAI,CAAU;gBAE/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAMxD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAA0D,EACnE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,6BAA8B,SAAQ,eAAe;IACjE,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,UAAU,SAAe,EACzB,OAAO,GAAE,MAA+Q,EACxR,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,MAA0F,EACnG,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAM5B;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,IAAI,yBAAyB;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAuJ,EAChK,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACxD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGzB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAA+H,EACxI,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,oBAAoB;gBAGhC,OAAO,GAAE,MAAoF,EAC7F,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;gBAGxC,OAAO,GAAE,MAAmE,EAC5E,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E"}
package/dist/errors.js CHANGED
@@ -45,6 +45,30 @@ export class MissingBrowserBinaryError extends ControllerError {
45
45
  this.browser = browser;
46
46
  }
47
47
  }
48
+ /**
49
+ * Stealth launch was REQUESTED (the opt-in is on) but the optional `patchright`
50
+ * dependency is not installed/importable. Patchright is an OPTIONAL dependency
51
+ * of `@webhands/core` imported lazily only when stealth is enabled, so a user
52
+ * who never opts in is not forced to install it (ADR-0002: stealth is one extra
53
+ * layer, not the default). When it IS opted into and missing, we refuse LOUDLY
54
+ * with this typed condition rather than silently falling back to vanilla
55
+ * Playwright, because a silent fallback would re-introduce the exact CDP
56
+ * automation tell the user asked us to remove WITHOUT telling them.
57
+ *
58
+ * Mirrors {@link MissingBrowserBinaryError}: a stable typed error whose brittle
59
+ * detection (the dynamic-import failure) is confined to one spot in the launch
60
+ * transport. The CLI can render the exact `pnpm add patchright` fix command by
61
+ * branching on {@link code}.
62
+ */
63
+ export class MissingStealthDependencyError extends ControllerError {
64
+ code = 'missing-stealth-dependency';
65
+ /** The optional package that must be installed to use stealth. */
66
+ dependency;
67
+ constructor(dependency = 'patchright', message = `Stealth launch is enabled but the optional "${dependency}" dependency is not installed. Install it with \`pnpm add ${dependency}\` (and \`${dependency} install chromium\` if you do not use channel: 'chrome'), or construct the transport without {stealth: true}.`, options) {
68
+ super(message, options);
69
+ this.dependency = dependency;
70
+ }
71
+ }
48
72
  /**
49
73
  * The named profile has not been set up yet: its dedicated profile directory
50
74
  * does not exist on disk. A profile is created by the headed `setup-profile`
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH;;;;;;GAMG;AACH,MAAM,OAAgB,eAAgB,SAAQ,KAAK;IAGlD,8EAA8E;IACrE,iBAAiB,GAAG,IAAa,CAAC;IAE3C,YAAY,OAAe,EAAE,OAA2B;QACvD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,sEAAsE;QACtE,yCAAyC;QACzC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC7B,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,yBAA0B,SAAQ,eAAe;IACpD,IAAI,GAAG,wBAAwB,CAAC;IACzC,6DAA6D;IACpD,OAAO,CAAS;IAEzB,YACC,OAAe,EACf,UAAkB,OAAO,OAAO,mCAAmC,EACnE,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,mBAAoB,SAAQ,eAAe;IAC9C,IAAI,GAAG,iBAAiB,CAAC;IAClC,kDAAkD;IACzC,OAAO,CAAS;IACzB,kEAAkE;IACzD,UAAU,CAAS;IAE5B,YACC,OAAe,EACf,UAAkB,EAClB,UAAkB,QAAQ,OAAO,oDAAoD,UAAU,IAAI,EACnG,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9B,CAAC;CACD;AAED;;;;;;GAMG;AACH,MAAM,OAAO,sBAAuB,SAAQ,eAAe;IACjD,IAAI,GAAG,qBAAqB,CAAC;IACtC,4EAA4E;IACnE,OAAO,CAAS;IAEzB,YACC,OAAe,EACf,UAAkB,oDAAoD,OAAO,mFAAmF,EAChK,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,oBAAqB,SAAQ,eAAe;IAC/C,IAAI,GAAG,mBAAmB,CAAC;IACpC,qDAAqD;IAC5C,QAAQ,CAAS;IAE1B,YACC,QAAgB,EAChB,UAAkB,+CAA+C,QAAQ,+DAA+D,EACxI,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IAC5C,IAAI,GAAG,gBAAgB,CAAC;IAEjC,YACC,UAAkB,2EAA2E,EAC7F,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,yBAA0B,SAAQ,eAAe;IACpD,IAAI,GAAG,wBAAwB,CAAC;IAEzC,YACC,UAAkB,0DAA0D,EAC5E,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC/C,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAuC,CAAC,iBAAiB,KAAK,IAAI;QACnE,OAAQ,KAA0B,CAAC,IAAI,KAAK,QAAQ,CACpD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,217 @@
1
+ import { type BrowserContext, type Page } from 'playwright';
2
+ import type { WebHandsPage, WaitCondition } from './seam.js';
3
+ /**
4
+ * The hand-host primitive (Phase 1 of the "hands" prd,
5
+ * `work/prds/tasked/hands-pluggable-page-capabilities.md`).
6
+ *
7
+ * A **hand** is in-process code that closes over the WebHandsPage and contributes named
8
+ * verbs (+ an optional `dispose`). This module is the host: it builds the
9
+ * scoped-but-LIVE {@link HandContext} from the live Playwright objects, lets
10
+ * each hand contribute its verbs, and composes them into the same {@link WebHandsPage}
11
+ * object the seam already exposes (see {@link composePage}).
12
+ *
13
+ * webhands' OWN eight verbs are themselves built-in hands over this host
14
+ * ({@link BUILT_IN_HANDS}), so the primitive is proven by self-application: if
15
+ * it can express webhands' `click`/`snapshot`/`cookies`, it can host a
16
+ * third-party hand the same way (Phase 2). This is a purely INTERNAL,
17
+ * behavior-preserving refactor — the verb composition that lived as a
18
+ * duplicated `page` object literal in BOTH Playwright transports now lives here
19
+ * once.
20
+ *
21
+ * INTERNAL-ONLY BOUNDARY (the prd's resolved Q2): this whole module is
22
+ * package-internal. {@link Hand}/{@link HandContext}/{@link composePage} are
23
+ * NOT exported from the package entry point (`index.ts`) in Phase 1; they go
24
+ * public in the separate Phase 2 task. The public seam (`seam.ts`) is
25
+ * unchanged.
26
+ *
27
+ * NO-LEAK / CROSS-BROWSER (ADR-0003, refined by the prd): the host is built
28
+ * INSIDE the Playwright transport(s) and uses only the Playwright
29
+ * `Page`/`BrowserContext` API — no CDP/Chromium-only types — so the live
30
+ * `pwPage` stays in-process and never crosses the seam, and the host introduces
31
+ * no Chromium-only dependency that would foreclose a future Firefox launch
32
+ * (only CDP-`attach` stays Chromium-bound, as today).
33
+ *
34
+ * TRUST MODEL (stated, not enforced here): hands are trusted, local, in-process
35
+ * peers with ZERO isolation between them (one live page, one process).
36
+ * Inter-hand reuse is ordinary Node composition (import & call), NOT a
37
+ * sibling-hand registry in the context — so {@link HandContext} carries live
38
+ * page access only.
39
+ */
40
+ /**
41
+ * The scoped-but-LIVE access a hand receives. It carries live page access ONLY
42
+ * (the trust model note above): the real Playwright {@link Page} and
43
+ * {@link BrowserContext} the hand operates against in-process, plus the
44
+ * lifecycle guard.
45
+ *
46
+ * - `pwPage` — the live Node-side Playwright `Page`. NEVER crosses the seam.
47
+ * - `context` — the live `BrowserContext`; the built-in `cookies`/`setCookies`
48
+ * hand proves it is needed (cookies are a context-level, not page-level,
49
+ * concern).
50
+ * - `ensureOpen` — the per-session lifecycle guard. Each verb calls it first so
51
+ * a verb invoked after the session closed rejects with `session is closed`
52
+ * (the seam's lifetime contract). The guard's "closed" state is owned by the
53
+ * per-transport session wiring (launch vs attach differ); the host only reads
54
+ * it through this function.
55
+ */
56
+ export interface HandContext {
57
+ readonly pwPage: Page;
58
+ readonly context: BrowserContext;
59
+ readonly ensureOpen: () => void;
60
+ }
61
+ /**
62
+ * What a hand contributes once given its {@link HandContext}: a set of named
63
+ * verbs (a subset of webhands' (eight) seam verbs, i.e. a `Partial` of the
64
+ * seam {@link WebHandsPage}) and an optional `dispose` for any in-process
65
+ * resource it set up.
66
+ *
67
+ * A hand may contribute several verbs (the built-in interaction hand contributes
68
+ * both `click` and `type`) — a hand is NOT a single verb. It is NOT a transport
69
+ * either: it does not `open` sessions. Nothing more than this is allowed (no
70
+ * lifecycle hooks, no event handlers, no MCP-definition objects) — those are
71
+ * either the transport's job (session lifecycle) or a later phase's.
72
+ */
73
+ export interface HandContribution {
74
+ readonly verbs: Partial<WebHandsPage>;
75
+ readonly dispose?: () => Promise<void> | void;
76
+ }
77
+ /**
78
+ * A hand: a capability MODULE that, given live page access, contributes verbs.
79
+ * It is a plain factory function so a hand is just ordinary in-process Node
80
+ * code closing over the {@link HandContext} — the exact shape webhands' own
81
+ * verbs already had, made explicit.
82
+ */
83
+ export type Hand = (ctx: HandContext) => HandContribution;
84
+ /**
85
+ * The composed result the host hands back to a transport's session wiring: the
86
+ * {@link WebHandsPage} (the seam object the verbs were merged into) and a single
87
+ * `dispose` that tears down every hand.
88
+ */
89
+ export interface ComposedHands {
90
+ readonly page: WebHandsPage;
91
+ /**
92
+ * Dispose every hand's resources. Hands are disposed in REVERSE registration
93
+ * order (LIFO, the natural teardown order for layered setup), and every
94
+ * hand's `dispose` is awaited even if an earlier one rejects, so one failing
95
+ * hand cannot strand another's cleanup. This disposes the HANDS only; tearing
96
+ * down the browser/context (and the order relative to this) is the
97
+ * per-transport session lifecycle's job, NOT the host's.
98
+ */
99
+ dispose(): Promise<void>;
100
+ }
101
+ /**
102
+ * Compose a set of hands over one live {@link HandContext} into a single
103
+ * {@link WebHandsPage}. This is the host primitive both Playwright transports call to
104
+ * build their session's verb surface — the SINGLE shared composition (no
105
+ * duplicated page-object literal).
106
+ *
107
+ * Composition is EAGER (exactly as the page object literal was built before):
108
+ * each hand is invoked once at session-open time and its verbs are merged into
109
+ * one page object. There is no lazy registration and no ordering effect on the
110
+ * verbs themselves (the eight built-in verbs have disjoint names). The returned
111
+ * {@link WebHandsPage} is validated to carry every verb the seam requires, so a missing
112
+ * built-in verb is a build-time/open-time failure here rather than an `undefined
113
+ * is not a function` at the call site.
114
+ */
115
+ export declare function composePage(ctx: HandContext, hands: readonly Hand[]): ComposedHands;
116
+ /** The `navigate` verb: go to a URL and let it settle on the `load` event. */
117
+ export declare const navigationHand: Hand;
118
+ /** The `snapshot` verb: the token-cheap a11y view, or `--full` raw DOM. */
119
+ export declare const snapshotHand: Hand;
120
+ /** The `click` + `type` verbs: page interaction by raw locator (ADR-0004). */
121
+ export declare const interactionHand: Hand;
122
+ /** The `eval` escape hatch: run a JS EXPRESSION in the page, return by value. */
123
+ export declare const evalHand: Hand;
124
+ /** The `wait` verb: pace actions by a condition (timeout/locator/navigation). */
125
+ export declare const waitHand: Hand;
126
+ /**
127
+ * The `cookies` + `setCookies` verbs. These prove the {@link HandContext} needs
128
+ * the `context`: cookies are a context-level, not page-level, concern, so this
129
+ * hand reaches `ctx.context`, not `ctx.pwPage`.
130
+ */
131
+ export declare const cookiesHand: Hand;
132
+ /**
133
+ * webhands' eight built-in verbs as built-in hands, in composition order. Both
134
+ * Playwright transports compose THIS exact set, so the verb surface is
135
+ * identical across launch and attach (the only legitimate difference is the
136
+ * per-transport SESSION LIFECYCLE, which is not a hand's concern).
137
+ */
138
+ export declare const BUILT_IN_HANDS: readonly Hand[];
139
+ /**
140
+ * Compose webhands' built-in hands over a live context into the seam's
141
+ * {@link WebHandsPage}. The convenience both transports call: `composePage(ctx,
142
+ * BUILT_IN_HANDS)`. The built-in hands set up no in-process resources, so the
143
+ * returned `dispose` is a no-op today; it exists so a transport can sequence
144
+ * hand-teardown before its own browser/context teardown once third-party hands
145
+ * (which may hold resources) are added in Phase 2.
146
+ */
147
+ export declare function composeBuiltInPage(ctx: HandContext): ComposedHands;
148
+ /**
149
+ * Compose webhands' built-in hands together with any explicitly-loaded
150
+ * third-party hands (Phase 2) over a live context. The third-party hands are
151
+ * composed AFTER the built-ins through the EXACT same {@link composePage} the
152
+ * built-ins use, so a loaded hand plugs into the same host: its verbs merge into
153
+ * the same seam {@link WebHandsPage} and its `dispose` is sequenced LIFO with the rest.
154
+ * A third-party hand may add NEW verbs (the common case) and, because later
155
+ * contributions win the merge, may also override a built-in verb — that is the
156
+ * operator's choice, made by the trust act of naming the hand (ADR-0007).
157
+ */
158
+ export declare function composeWithHands(ctx: HandContext, extraHands: readonly Hand[]): ComposedHands;
159
+ /**
160
+ * Run the `wait` verb's three forms (PRD story 10) against a Playwright page.
161
+ *
162
+ * - `timeout` — pace by a fixed delay (`waitForTimeout`), so an agent can act
163
+ * like a human and let XHR-rendered content land.
164
+ * - `locator` — block until the addressed element appears (`Locator.waitFor()`),
165
+ * the form for content rendered AFTER `goto` settled on `load`.
166
+ * - `navigation` — block until the NEXT navigation settles to `load`. We use
167
+ * `waitForNavigation()` even though Playwright marks it `@deprecated` ("racy,
168
+ * use waitForURL"): that deprecation targets in-process TEST code that can arm
169
+ * the wait BEFORE the action and pass a target URL. Neither holds here. Across
170
+ * this seam verbs are DISCRETE sequential calls (`click` then `wait`), so we
171
+ * CANNOT arm before the trigger; and the realistic trigger is an async,
172
+ * JS-driven transition (a redirect / SPA route change that fires AFTER the
173
+ * agent's action, the "let XHR-rendered content load" case of story 10), so
174
+ * "wait for the NEXT navigation" is exactly right — whereas `waitForLoadState`
175
+ * would see the already-loaded current page and return before the pending
176
+ * transition. `waitForURL` is unusable because the verb has no target URL by
177
+ * design (the agent waits for "a navigation", not a known address). (See the
178
+ * task's ## Decisions note.)
179
+ *
180
+ * Shared by both Playwright transports (via the `wait` built-in hand) so the
181
+ * verb behaviour stays identical (no parallel second implementation).
182
+ */
183
+ export declare function waitFor(page: Page, condition: WaitCondition): Promise<void>;
184
+ /**
185
+ * Resolve a raw Playwright locator EXPRESSION (ADR-0004) against the page. The
186
+ * verb surface passes locator expressions like `getByRole('button', …)`; we
187
+ * evaluate them in a small sandbox where `page`/`p` is the page, so the full
188
+ * Playwright locator grammar is available without leaking the type across the
189
+ * seam.
190
+ *
191
+ * One resolution path for both transports (via the built-in interaction/wait
192
+ * hands), so there is no parallel addressing scheme.
193
+ */
194
+ export declare function resolveLocator(page: Page, expression: string): import("playwright").Locator;
195
+ /**
196
+ * Run the `click` verb against a Playwright page (PRD story 8), shared by both
197
+ * Playwright transports (via the built-in interaction hand) so the verb behaves
198
+ * identically (mirrors {@link waitFor}; no parallel second implementation).
199
+ *
200
+ * First try a normal `Locator.click()`, which AUTO-WAITS for the element to be
201
+ * visible and actionable — the right behaviour for a real button. A hidden
202
+ * custom input (the case the prd calls out) NEVER becomes actionable, so that
203
+ * click times out; on a Playwright `TimeoutError` we fall back to
204
+ * `dispatchEvent('click')`, which fires a click WITHOUT the actionability
205
+ * checks. The fallback is deliberately the documented Playwright escape (a
206
+ * sibling to the `eval` hatch, ADR-0004), not a reimplemented click: we keep
207
+ * the locator a raw resolved expression and only change HOW the resolved
208
+ * locator is clicked.
209
+ *
210
+ * Only a timeout triggers the fallback. The fallback `dispatchEvent` is itself
211
+ * bounded by the same short timeout, so a locator that resolves NO element (a
212
+ * bad locator) surfaces its timeout quickly instead of hanging the dispatch on
213
+ * Playwright's 30s default — the dispatch escape is for elements that EXIST but
214
+ * are not actionable (hidden custom inputs), not for absent ones.
215
+ */
216
+ export declare function clickLocator(page: Page, expression: string): Promise<void>;
217
+ //# sourceMappingURL=hand-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hand-host.d.ts","sourceRoot":"","sources":["../src/hand-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAEX,YAAY,EAGZ,aAAa,EACb,MAAM,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B;;;;;;;OAOG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,SAAS,IAAI,EAAE,GACpB,aAAa,CAgCf;AAsDD,8EAA8E;AAC9E,eAAO,MAAM,cAAc,EAAE,IAe3B,CAAC;AAEH,2EAA2E;AAC3E,eAAO,MAAM,YAAY,EAAE,IAyBzB,CAAC;AAEH,8EAA8E;AAC9E,eAAO,MAAM,eAAe,EAAE,IAW5B,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,IAoBrB,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,QAAQ,EAAE,IAOrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,IAYxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,IAAI,EAOzC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,SAAS,IAAI,EAAE,GACzB,aAAa,CAEf;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,OAAO,CAC5B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gCAO5D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CACjC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAYf"}