webhands 0.0.0 → 0.1.7

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/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.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { createCli } from './cli.js';
3
+ /**
4
+ * The executable entry for the `webhands` binary. It builds the
5
+ * `incur` CLI (with its default, real-browser session provider) and serves it:
6
+ * `serve()` parses argv, runs the matched command, writes the structured output
7
+ * envelope, and handles `--mcp` / `--llms` / `mcp add` / `skills add` for free.
8
+ *
9
+ * Kept separate from the builder (`cli.ts`) so tests drive the builder with an
10
+ * injected provider via `serve(argv, {stdout, exit})` without spawning a real
11
+ * browser, and only THIS file performs the real `.serve()`.
12
+ */
13
+ void createCli().serve();
14
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAC;AAEnC;;;;;;;;;GASG;AACH,KAAK,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { Cli } from 'incur';
2
+ import { buildPrompt, resolveProfileLocation, type OpenTarget, type RunningSessionServer } from '@webhands/core';
3
+ import { type SessionProvider } from './session-provider.js';
4
+ import { setupProfile } from '@webhands/core';
5
+ /**
6
+ * The CLI binary name. Used both as the `incur` CLI name and (echoed back from
7
+ * `c.name`) inside every fix-command message, so a suggested command always
8
+ * matches how the user invoked the tool.
9
+ */
10
+ export declare const CLI_NAME = "webhands";
11
+ /**
12
+ * The default profile name a page verb / launch / setup-profile targets when
13
+ * the user does not name one. A single, predictable default keeps the common
14
+ * case a one-word command; a user with several sessions passes `--profile`.
15
+ */
16
+ export declare const DEFAULT_PROFILE = "default";
17
+ /** Injectable dependencies, so CLI-level tests exercise the WIRING with no real browser. */
18
+ export interface CliDeps {
19
+ /**
20
+ * How a verb command obtains a live session. Defaults to the v1 Playwright
21
+ * provider; tests inject a stub-backed (or throwing) provider to assert the
22
+ * envelope/cta/manifest and the typed-error fix messages without a browser.
23
+ * See {@link SessionProvider}.
24
+ */
25
+ readonly sessionProvider?: SessionProvider;
26
+ /**
27
+ * The `setup-profile` orchestration (defaults to `core`'s `setupProfile`).
28
+ * Injectable so the `setup-profile` command's wiring is testable headlessly.
29
+ */
30
+ readonly setupProfile?: typeof setupProfile;
31
+ /** Overrides for the controller home root (tests pass a temp dir). */
32
+ readonly home?: {
33
+ root?: string;
34
+ env?: NodeJS.ProcessEnv;
35
+ };
36
+ /**
37
+ * How the `serve` command brings up the single long-lived session (ADR-0005).
38
+ * Defaults to {@link startSessionServer} bound to the v1 Playwright transports;
39
+ * injectable so `serve`/`stop` wiring is testable with a stub transport and no
40
+ * real browser, and so a test can drive the server lifecycle deterministically.
41
+ */
42
+ readonly serveSession?: ServeSession;
43
+ /**
44
+ * The version string reported by `--version`, the help header, and the MCP
45
+ * server. Defaults to {@link VERSION} (this package's `package.json` version);
46
+ * injectable so a test can assert the wiring deterministically.
47
+ */
48
+ readonly version?: string;
49
+ }
50
+ /**
51
+ * The `serve`-command seam: bring up the ONE long-lived session for a target and
52
+ * return the running server (its advertised endpoint + an explicit `stop`). The
53
+ * default wraps `core`'s {@link startSessionServer} with the v1 Playwright
54
+ * transports; tests inject a stub-backed one.
55
+ */
56
+ export type ServeSession = (target: OpenTarget, options: {
57
+ root?: string;
58
+ env?: NodeJS.ProcessEnv;
59
+ }, launchPolicy?: LaunchPolicy) => Promise<RunningSessionServer>;
60
+ /**
61
+ * Transport-construction policy for a LAUNCH-mode open: the opt-in stealth
62
+ * toggle and the optional system browser to drive. Kept SEPARATE from
63
+ * {@link OpenTarget} so the seam stays free of Playwright/CDP/stealth concepts
64
+ * (ADR-0003); it rides alongside the target into the transport constructor only.
65
+ * Ignored for attach (the user's own browser is reused as-is).
66
+ */
67
+ export interface LaunchPolicy {
68
+ /** Opt-in Patchright-backed stealth launch. Default off. */
69
+ readonly stealth?: boolean;
70
+ /**
71
+ * Drive a browser already installed on the system (e.g. `'chrome'`) instead
72
+ * of the bundled Chromium. Omit for bundled Chromium.
73
+ */
74
+ readonly systemBrowser?: string;
75
+ }
76
+ /**
77
+ * Build the `incur` CLI that wraps `core`'s verb surface (PRD Implementation
78
+ * Decisions — `cli`; stories 12-14, 17).
79
+ *
80
+ * Because it is built on `incur`, the SAME binary is also an MCP server
81
+ * (`--mcp` / `mcp add`) and emits a skills / `--llms` manifest with NO bespoke
82
+ * MCP code: those come from incur for free. Each command declares zod
83
+ * `args`/`options`/`output` schemas (so input is validated and output has a
84
+ * known shape), returns the structured TOON/JSON envelope, and attaches `cta`
85
+ * next-verb hints. Typed `core` errors (missing binary / missing profile) are
86
+ * mapped to the user-facing message + exact fix command via
87
+ * {@link mapControllerError}.
88
+ *
89
+ * Returns the built `Cli` WITHOUT calling `.serve()`, so a test can drive it via
90
+ * `serve(argv, {stdout, exit})` or `cli.fetch(req)` and the bin entry owns the
91
+ * real `.serve()`.
92
+ */
93
+ export declare function createCli(deps?: CliDeps): Cli.Cli<{}, undefined, undefined, undefined>;
94
+ export { buildPrompt, resolveProfileLocation };
95
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAI,MAAM,OAAO,CAAC;AAC7B,OAAO,EACN,WAAW,EACX,sBAAsB,EAWtB,KAAK,UAAU,EACf,KAAK,oBAAoB,EAKzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAG5C;;;;GAIG;AACH,eAAO,MAAM,QAAQ,aAAa,CAAC;AAMnC;;;;GAIG;AACH,eAAO,MAAM,eAAe,YAAY,CAAC;AAEzC,4FAA4F;AAC5F,MAAM,WAAW,OAAO;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;IAC5C,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;KAAC,CAAC;IACzD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAC,EACjD,YAAY,CAAC,EAAE,YAAY,KACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC;AAwID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,OAAY,gDA+hB3C;AAuED,OAAO,EAAC,WAAW,EAAE,sBAAsB,EAAC,CAAC"}