webhands 0.0.0 → 0.1.6
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/LICENSE +661 -0
- package/README.md +112 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +79 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +639 -0
- package/dist/cli.js.map +1 -0
- package/dist/errors.d.ts +45 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/session-provider.d.ts +52 -0
- package/dist/session-provider.d.ts.map +1 -0
- package/dist/session-provider.js +28 -0
- package/dist/session-provider.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +12 -0
- package/dist/version.js.map +1 -0
- package/package.json +58 -2
- package/src/bin.ts +14 -0
- package/src/cli.ts +797 -0
- package/src/errors.ts +114 -0
- package/src/index.ts +36 -0
- package/src/session-provider.ts +70 -0
- package/src/version.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
## Security note (the `serve` endpoint runs arbitrary code)
|
|
101
|
+
|
|
102
|
+
The page verbs execute caller-supplied expressions: `eval` runs a JS expression
|
|
103
|
+
in the page, and a `click`/`type` locator is a raw Playwright locator EXPRESSION
|
|
104
|
+
the controller evaluates (see
|
|
105
|
+
[`docs/adr/0004`](docs/adr/0004-verb-surface-exposes-playwright-locator-semantics.md)).
|
|
106
|
+
That is by design for a LOCAL tool driven by its own agent against your own
|
|
107
|
+
session, but it means the running `serve` endpoint is a code-execution surface.
|
|
108
|
+
|
|
109
|
+
- **Do NOT expose the `serve` endpoint to untrusted callers.** Keep it bound to
|
|
110
|
+
localhost (the default); never bind it to a public interface or hand its URL to
|
|
111
|
+
code you do not trust. Anyone who can call it can run arbitrary JavaScript in
|
|
112
|
+
your logged-in session.
|
package/dist/bin.d.ts
ADDED
|
@@ -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
|
package/dist/bin.js.map
ADDED
|
@@ -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,79 @@
|
|
|
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
|
+
}) => Promise<RunningSessionServer>;
|
|
60
|
+
/**
|
|
61
|
+
* Build the `incur` CLI that wraps `core`'s verb surface (PRD Implementation
|
|
62
|
+
* Decisions — `cli`; stories 12-14, 17).
|
|
63
|
+
*
|
|
64
|
+
* Because it is built on `incur`, the SAME binary is also an MCP server
|
|
65
|
+
* (`--mcp` / `mcp add`) and emits a skills / `--llms` manifest with NO bespoke
|
|
66
|
+
* MCP code: those come from incur for free. Each command declares zod
|
|
67
|
+
* `args`/`options`/`output` schemas (so input is validated and output has a
|
|
68
|
+
* known shape), returns the structured TOON/JSON envelope, and attaches `cta`
|
|
69
|
+
* next-verb hints. Typed `core` errors (missing binary / missing profile) are
|
|
70
|
+
* mapped to the user-facing message + exact fix command via
|
|
71
|
+
* {@link mapControllerError}.
|
|
72
|
+
*
|
|
73
|
+
* Returns the built `Cli` WITHOUT calling `.serve()`, so a test can drive it via
|
|
74
|
+
* `serve(argv, {stdout, exit})` or `cli.fetch(req)` and the bin entry owns the
|
|
75
|
+
* real `.serve()`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createCli(deps?: CliDeps): Cli.Cli<{}, undefined, undefined, undefined>;
|
|
78
|
+
export { buildPrompt, resolveProfileLocation };
|
|
79
|
+
//# 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,KAC7C,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAiGnC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,OAAY,gDAkhB3C;AAgED,OAAO,EAAC,WAAW,EAAE,sBAAsB,EAAC,CAAC"}
|