@rester159/blacktip 0.1.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 (63) hide show
  1. package/AGENTS.md +249 -0
  2. package/LICENSE +38 -0
  3. package/README.md +234 -0
  4. package/dist/behavioral/calibration.d.ts +145 -0
  5. package/dist/behavioral/calibration.d.ts.map +1 -0
  6. package/dist/behavioral/calibration.js +242 -0
  7. package/dist/behavioral/calibration.js.map +1 -0
  8. package/dist/behavioral-engine.d.ts +156 -0
  9. package/dist/behavioral-engine.d.ts.map +1 -0
  10. package/dist/behavioral-engine.js +521 -0
  11. package/dist/behavioral-engine.js.map +1 -0
  12. package/dist/blacktip.d.ts +289 -0
  13. package/dist/blacktip.d.ts.map +1 -0
  14. package/dist/blacktip.js +1574 -0
  15. package/dist/blacktip.js.map +1 -0
  16. package/dist/browser-core.d.ts +47 -0
  17. package/dist/browser-core.d.ts.map +1 -0
  18. package/dist/browser-core.js +375 -0
  19. package/dist/browser-core.js.map +1 -0
  20. package/dist/cli.d.ts +20 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +226 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/element-finder.d.ts +42 -0
  25. package/dist/element-finder.d.ts.map +1 -0
  26. package/dist/element-finder.js +240 -0
  27. package/dist/element-finder.js.map +1 -0
  28. package/dist/evasion.d.ts +39 -0
  29. package/dist/evasion.d.ts.map +1 -0
  30. package/dist/evasion.js +488 -0
  31. package/dist/evasion.js.map +1 -0
  32. package/dist/fingerprint.d.ts +19 -0
  33. package/dist/fingerprint.d.ts.map +1 -0
  34. package/dist/fingerprint.js +171 -0
  35. package/dist/fingerprint.js.map +1 -0
  36. package/dist/index.d.ts +19 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +14 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/logging.d.ts +13 -0
  41. package/dist/logging.d.ts.map +1 -0
  42. package/dist/logging.js +42 -0
  43. package/dist/logging.js.map +1 -0
  44. package/dist/observability.d.ts +69 -0
  45. package/dist/observability.d.ts.map +1 -0
  46. package/dist/observability.js +189 -0
  47. package/dist/observability.js.map +1 -0
  48. package/dist/proxy-pool.d.ts +101 -0
  49. package/dist/proxy-pool.d.ts.map +1 -0
  50. package/dist/proxy-pool.js +156 -0
  51. package/dist/proxy-pool.js.map +1 -0
  52. package/dist/snapshot.d.ts +59 -0
  53. package/dist/snapshot.d.ts.map +1 -0
  54. package/dist/snapshot.js +91 -0
  55. package/dist/snapshot.js.map +1 -0
  56. package/dist/types.d.ts +243 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +15 -0
  59. package/dist/types.js.map +1 -0
  60. package/examples/01-basic-navigate.ts +40 -0
  61. package/examples/02-login-with-mfa.ts +68 -0
  62. package/examples/03-agent-serve-mode.md +98 -0
  63. package/package.json +62 -0
package/AGENTS.md ADDED
@@ -0,0 +1,249 @@
1
+ # AGENTS.md — BlackTip for AI agents
2
+
3
+ **Read this before driving BlackTip.** This file is shipped with every BlackTip install and is designed to be auto-loaded by agent frameworks (Claude Code, Cursor, aider, etc.) as context for the consuming project. If you are an LLM-backed agent, treat this as the user manual.
4
+
5
+ BlackTip is a browser instrument. YOU are the agent. BlackTip provides the hands; you provide the brain.
6
+
7
+ ---
8
+
9
+ ## The core loop
10
+
11
+ 1. **Read the source documents first.** If the user gave you a PDF, image, or file to submit, read it BEFORE touching the browser. Extract names, dates, amounts, IDs. Never guess.
12
+ 2. **Start the server.** `npx blacktip serve` (or create a `BlackTip` instance and call `await bt.serve(port)`).
13
+ 3. **Send one command at a time.** After each command, read the returned bundle: `{ok, result, url, title, screenshotPath, screenshotB64, durationMs}`.
14
+ 4. **Look at the screenshot.** The returned `screenshotB64` (or the saved file at `screenshotPath`) is the ground truth for what the page currently looks like. Do NOT pre-script a sequence of steps — look, decide, act, repeat.
15
+ 5. **Ask when uncertain.** Don't guess credentials, form values, patient names, account types, or confirmation decisions. Ask the user.
16
+
17
+ ---
18
+
19
+ ## Rules (always follow)
20
+
21
+ ### 1. Read input documents before opening the browser
22
+
23
+ If the user provides a bill, form, or screenshot, extract all relevant data first. Every minute spent understanding the source saves ten minutes of corrections in the browser. This rule is non-negotiable.
24
+
25
+ ### 2. Screenshot between every decision
26
+
27
+ BlackTip's `send` response includes a base64 screenshot of the page after each command. Look at it before deciding the next step. The page may have changed, loaded slowly, or shown an error you didn't expect. Do NOT assume the next step works because the last one did.
28
+
29
+ ### 3. Use `clickText()` for visible text, `click()` for selectors
30
+
31
+ - `bt.clickText("Sign in", { nth: 0 })` — uses Playwright's locator API. Correctly handles React, Angular, Okta, and other framework-driven components. Prefer this over CSS selectors when the text is visible on the page.
32
+ - `bt.click("#submit-btn")` — use when you have a reliable CSS or XPath selector.
33
+ - `bt.clickRole("button", { name: "Submit" })` — use for ARIA-role-based matches.
34
+
35
+ All three auto-detect high-importance actions (submit, pay, confirm, place order, delete, etc.) and apply longer pre-action hesitation. You can override with `importance: 'low' | 'normal' | 'high'`.
36
+
37
+ ### 4. Use `paste: true` for form filling
38
+
39
+ `bt.type(selector, text, { paste: true })` uses Playwright's `fill()` which correctly triggers React/Angular synthetic events. This is the fast path and should be your default for form fields.
40
+
41
+ Without `paste: true`, BlackTip simulates keystroke-by-keystroke typing with digraph-aware timing and occasional typos. Slower but looks more human. Use when a site profiles typing dynamics.
42
+
43
+ ### 5. Use `inspect()` to diagnose selectors
44
+
45
+ When a click or type fails, use `bt.inspect(selector)` before retrying. It returns `{exists, visible, tagName, text, attributes, boundingBox}` — one call replaces several hand-written `executeJS` queries.
46
+
47
+ ### 6. Use `listOptions()` for Angular/React custom dropdowns
48
+
49
+ Custom dropdowns are NOT `<select>` elements. Pattern:
50
+
51
+ ```js
52
+ await bt.click("#state-dropdown_button"); // Open the combobox
53
+ const options = await bt.listOptions("#state-dropdown"); // List options
54
+ // options: [{id: "state-dropdown_option-0", text: "Alabama"}, ...]
55
+ await bt.click(options.find(o => o.text === "California").id);
56
+ ```
57
+
58
+ ### 7. Use `waitForStable()` instead of fixed sleeps
59
+
60
+ Don't write `await new Promise(r => setTimeout(r, 3000))`. That's always wrong — either too short (you miss the event) or too long (you waste time). Use:
61
+
62
+ - `await bt.waitForStable({ networkIdleMs: 500, domIdleMs: 500 })` — page has settled
63
+ - `await bt.waitForText("Success", { timeout: 10000 })` — wait for a specific string
64
+ - `await bt.waitFor("#some-selector", { timeout: 10000 })` — wait for an element
65
+
66
+ ### 8. Check `didRequestFireSince` before retrying risky clicks
67
+
68
+ If a click might have burned a rate-limited or lockout-protected attempt (password, 2FA, payment), check whether the submit actually reached the server:
69
+
70
+ ```js
71
+ await bt.click(".submit-password");
72
+ const submitted = await bt.didRequestFireSince(/idp\/idx\/challenge\/answer/, 3000);
73
+ if (!submitted) {
74
+ // The click didn't fire. Safe to retry.
75
+ } else {
76
+ // It DID fire. Check if it succeeded before retrying.
77
+ }
78
+ ```
79
+
80
+ This is how you avoid locking an account after a "did my click work?" moment.
81
+
82
+ ### 9. Use `dismissOverlays()` proactively on sites with chat widgets
83
+
84
+ Chat widgets, cookie banners, and "we value your feedback" modals intercept clicks. `bt.dismissOverlays()` hides known overlay patterns (Intercom, Drift, Zendesk, OneTrust, Medallia, generic cookie consent) and returns the count hidden. Call it once after navigation on sites that have these, then proceed normally.
85
+
86
+ BlackTip's `click` / `clickText` / `clickRole` also auto-detect interception and auto-dismiss overlays before retrying, so in most cases you don't need to call it explicitly.
87
+
88
+ ### 10. MFA: use `pauseForInput`
89
+
90
+ When you hit an MFA / OTP / verification code prompt:
91
+
92
+ ```js
93
+ await bt.click(".send-sms-button");
94
+ const code = await bt.pauseForInput({
95
+ prompt: 'Enter the SMS code sent to your phone',
96
+ validate: /^\d{6}$/,
97
+ timeoutMs: 300_000,
98
+ });
99
+ await bt.type('input[name="code"]', code, { paste: true });
100
+ await bt.click(".verify");
101
+ ```
102
+
103
+ The BlackTip server sends a `{paused:true, pauseId, prompt}` frame to the client. The client (you) relays the prompt to the user, collects their answer, and resumes with:
104
+
105
+ ```bash
106
+ npx blacktip resume <pauseId> "<value>"
107
+ ```
108
+
109
+ ### 11. Fail fast
110
+
111
+ Use `timeout: 10000` and `retryAttempts: 2` as defaults. If an action fails, inspect the page with `bt.inspect()` and a screenshot rather than retrying blindly. Blind retries on lockout-protected forms burn attempts you cannot get back.
112
+
113
+ ### 12. Ask before destructive or irreversible actions
114
+
115
+ Before clicking Submit on a form that can't be unsent (insurance claim, payment, purchase, account deletion, account creation with verified email), **stop and ask the user to confirm**. Show them the filled-in state from the screenshot. Wait for an explicit "confirmed" / "submit" / "go" before proceeding.
116
+
117
+ ### 13. Never guess secrets
118
+
119
+ Passwords, MFA codes, payment details, SSNs, routing numbers, patient information — if the user didn't give it to you explicitly, ask. Don't infer from memory, don't infer from session state, don't pattern-match from other accounts.
120
+
121
+ ---
122
+
123
+ ## Common mistakes (don't repeat these)
124
+
125
+ - **Pre-scripting an entire flow.** Breaks on the first unexpected page state. Always look at the screenshot after each step.
126
+ - **Using `executeJS("el.click()")` for React/Okta buttons.** DOM clicks don't trigger framework event handlers. Use `clickText()` or `click()` which dispatch real pointer events.
127
+ - **Not reading screenshots between actions.** The most common failure mode. If you're not looking at the screenshot, you're flying blind.
128
+ - **Guessing form values instead of reading source documents.** Led to submitting the wrong patient name on an Anthem claim during development.
129
+ - **Long timeouts (30s+).** You're waiting on a page that's already broken. Fail fast, inspect, adapt.
130
+ - **Treating data: URLs as fully functional.** `patchright` blocks inline `<script>` execution on `data:` URLs as a stealth measure. Use `bt.executeJS()` after navigate to set up page state, or use a real HTTP server for fixtures.
131
+ - **Retrying a failed login without checking `didRequestFireSince`.** You might have already burned an attempt without realizing it.
132
+ - **Calling `.removeAllListeners()` with no argument.** It removes BlackTip's default `error` handler and Node's EventEmitter will crash on the next emitted error. Use `.removeAllListeners('action')` per-event instead.
133
+ - **Not clearing prior drafts on form-heavy sites.** Some sites (Anthem, insurance portals) save drafts automatically. Check for and delete stale drafts before starting a new submission if the site supports it.
134
+ - **Assuming BlackTip handles headless.** It doesn't. Every profile is headful. `headless: true` in config is ignored.
135
+
136
+ ---
137
+
138
+ ## Decision tree: common situations
139
+
140
+ **"I clicked Next but the page didn't change."**
141
+
142
+ 1. Take a new screenshot via `bt.screenshot({path:'shot.png'})`. Look carefully — sometimes the page DID change and the error is a banner on top of the same layout.
143
+ 2. Check the URL: `bt.executeJS('location.href')`.
144
+ 3. Check if a request fired: `bt.didRequestFireSince(/your-endpoint/, 5000)`.
145
+ 4. If no request fired, the click was swallowed. Try `bt.dismissOverlays()` then click again, or use a direct CSS selector.
146
+ 5. If a request DID fire and the URL didn't change, there's an error on the page. `bt.extractText('body')` or look at the screenshot for error messaging.
147
+
148
+ **"I can't find the selector I need."**
149
+
150
+ 1. `bt.inspect('#my-guess')` — verify what you think exists actually does.
151
+ 2. `bt.executeJS("[...document.querySelectorAll('button')].map(b => ({id: b.id, text: b.textContent.trim().slice(0,40)}))")` — enumerate candidates.
152
+ 3. For dropdowns, `bt.listOptions('#dropdown-id')` gets the Angular-style option ids.
153
+ 4. Try `bt.clickText("visible text")` as a fallback — often easier than finding the right CSS.
154
+
155
+ **"There's a chat widget blocking my click."**
156
+
157
+ 1. `bt.dismissOverlays()` — returns `{hidden: N, selectors: [...]}`.
158
+ 2. Retry the click. BlackTip's click functions also auto-dismiss before falling back to force-click, so you usually don't need step 1.
159
+
160
+ **"I'm about to click Submit on something important."**
161
+
162
+ 1. Take a screenshot first. Read it carefully.
163
+ 2. Summarize to the user what's about to happen (which patient, which amount, which destination).
164
+ 3. Wait for explicit confirmation.
165
+ 4. Only then click. Use `importance: 'high'` is auto-applied for buttons labeled Submit/Pay/Confirm, so you don't need to pass it manually.
166
+
167
+ **"The flow hits MFA."**
168
+
169
+ 1. Click the "send code" button.
170
+ 2. Call `bt.pauseForInput({prompt: 'Enter SMS code', validate: /^\d{6}$/})`.
171
+ 3. The BlackTip server forwards the prompt to your client. You relay it to the user.
172
+ 4. When the user provides the code, call `npx blacktip resume <pauseId> "<code>"` (or send `RESUME <id>\n<value>` to the TCP socket).
173
+ 5. The paused command resumes with the code and you proceed.
174
+
175
+ **"I'm uncertain whether a previous step succeeded."**
176
+
177
+ Don't retry blindly. Use:
178
+ - `bt.didRequestFireSince(pattern, ms)` for network-level confirmation
179
+ - `bt.waitForText(expectedText, {timeout})` for content-level confirmation
180
+ - `bt.inspect(selector).attributes` for state-level confirmation
181
+
182
+ ---
183
+
184
+ ## Server mode protocol
185
+
186
+ Every `send` returns a JSON bundle (one line, DELIMITER `\n__END__\n`):
187
+
188
+ ```json
189
+ {
190
+ "ok": true,
191
+ "result": <return value of the JS command, if any>,
192
+ "url": "https://current.page/",
193
+ "title": "Current Page Title",
194
+ "screenshotPath": "shot.png",
195
+ "screenshotB64": "<base64 PNG>",
196
+ "screenshotBytes": 41234,
197
+ "durationMs": 432
198
+ }
199
+ ```
200
+
201
+ On error:
202
+
203
+ ```json
204
+ {
205
+ "ok": false,
206
+ "error": "Error message",
207
+ "url": "https://where.it.failed/",
208
+ "screenshotPath": "shot.png",
209
+ "screenshotB64": "<base64 PNG>",
210
+ "durationMs": 1234
211
+ }
212
+ ```
213
+
214
+ On pause:
215
+
216
+ ```json
217
+ {
218
+ "ok": true,
219
+ "paused": true,
220
+ "pauseId": "pause-1712345678-12345",
221
+ "prompt": "Enter the SMS code"
222
+ }
223
+ ```
224
+
225
+ You respond with a `RESUME <pauseId>\n<value>` frame (ended with `\n__END__\n`) and the paused command continues.
226
+
227
+ Batch mode accepts a `BATCH\n<json array of commands>` frame and returns `{ok, bundles: [bundle, bundle, ...]}` — useful for pipelining linear flows without per-command round trips.
228
+
229
+ ---
230
+
231
+ ## Behavioral knobs
232
+
233
+ - `behaviorProfile: 'human'` — realistic timings (default for agent use)
234
+ - `behaviorProfile: 'scraper'` — faster, less human, used in tests
235
+ - `importance: 'high'` on `click` / `clickText` / `type` — longer pre-action hesitation. Auto-applied when button text matches common submit/pay/confirm patterns.
236
+ - `bt.waitForStable({networkIdleMs: 500, domIdleMs: 500})` — better than fixed sleeps
237
+ - `bt.generateReadingPause(textLength)` — estimate how long a human would take to read a block of text, returned as milliseconds
238
+
239
+ ---
240
+
241
+ ## Things BlackTip will not do for you
242
+
243
+ - **It will not log you in.** You tell it the username and password.
244
+ - **It will not read your SMS.** It pauses and asks you via `pauseForInput`.
245
+ - **It will not solve captchas.** You integrate a solver if needed.
246
+ - **It will not write tests for your flow.** That's still your job.
247
+ - **It will not understand intent.** `bt.clickText("Submit")` clicks "Submit" — it doesn't verify that clicking "Submit" is the right thing to do in your context.
248
+
249
+ If you want an opinion from the tool, you're asking the wrong tool. BlackTip provides primitives; the agent is the planner.
package/LICENSE ADDED
@@ -0,0 +1,38 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BlackTip Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ ACCEPTABLE USE NOTICE
26
+
27
+ BlackTip is a dual-use tool. It is designed for authorized security testing,
28
+ security research, browser fingerprinting research, your own accounts on
29
+ services whose Terms of Service you have accepted, legitimate automation where
30
+ the site's ToS permits automated access, and defensive anti-bot development.
31
+
32
+ It is NOT designed for unauthorized access, fraud, circumventing paywalls,
33
+ scraping sites that explicitly forbid automation, identity-related offenses,
34
+ or any activity that would violate computer fraud laws in your jurisdiction.
35
+
36
+ The authors disclaim responsibility for misuse. By using this software, you
37
+ represent that your use is lawful and consistent with the terms of service
38
+ of any site you access.
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # BlackTip
2
+
3
+ **Stealth browser instrument for AI agents.** Real Chrome + patchright CDP stealth + human-calibrated behavioral simulation. BlackTip is the hands, you (or your agent) are the brain.
4
+
5
+ BlackTip is not an agent. It does not parse natural language, does not plan, and does not decide what to click. It exposes primitives — `navigate`, `click`, `type`, `scroll`, `screenshot`, `waitForStable` — and wraps every action in human behavior that defeats bot detection.
6
+
7
+ **If you are an AI agent reading this, load `AGENTS.md` next.** It contains the rules, patterns, and common mistakes you need to know before driving BlackTip. The `README.md` is for humans evaluating the tool; `AGENTS.md` is for agents using it.
8
+
9
+ ---
10
+
11
+ ## What's different about BlackTip
12
+
13
+ Most open-source "stealth browser" projects are Playwright plus a JavaScript shim that overrides `navigator.webdriver`. That's table stakes and it's 2018-era thinking. Modern bot detection fingerprints the TLS ClientHello, the HTTP/2 frame order, the GPU through ANGLE, the canvas shader output, and — increasingly — your mouse dynamics and keystroke timing.
14
+
15
+ BlackTip's architecture:
16
+
17
+ | Layer | Approach | What it defeats |
18
+ |---|---|---|
19
+ | TLS / HTTP/2 fingerprint | Uses real Chrome Stable via Playwright `channel: 'chrome'`. ClientHello is genuine Chrome's, including rotating GREASE. | JA3 / JA4 / Akamai HTTP/2 fingerprinting |
20
+ | CDP / webdriver detection | `patchright` drop-in replacement for Playwright. Patches `Runtime.Enable` leak, Error stack hooks, `console.debug` hooks, automation string artifacts. | CreepJS headless/stealth panels, bot.sannysoft.com's webdriver check |
21
+ | GPU / canvas / audio | Real GPU rendered through ANGLE (no SwiftShader). Seeded canvas noise and audio noise tied to profile name for cross-session stability. | Canvas fingerprinting, audio fingerprinting, WebGL identification |
22
+ | Behavioral fidelity | Bézier mouse paths with Fitts' Law movement time, digraph-aware typing with typos/corrections, scroll deceleration, importance-scaled hesitation on submit/pay/confirm buttons, reading pause estimation. Calibratable against real mouse-dynamics datasets. | Behavioral biometrics (BioCatch, NuData, SecuredTouch) |
23
+ | Click robustness | Live bounding-box re-read before click (DOM reflow resistance), pre-click overlay detection, automatic overlay dismissal, force-click fallback. | Dynamic pages, chat widgets, cookie banners |
24
+ | Agent interface | TCP serve mode with bundled JSON responses (URL, title, screenshot, result in one frame). Batch command support. pause/resume for MFA. `--file` / `--stdin` inputs to eliminate shell-escape hell. | Orchestration overhead that makes agents slow |
25
+
26
+ **Detector scoreboard** (live as of last run; see `planning/baselines/` for timestamped artifacts):
27
+
28
+ - bot.sannysoft.com — 31 pass / 0 fail
29
+ - CreepJS — Grade A, 0% headless, 0% stealth
30
+ - tls.peet.ws — JA4 matches real Chrome with rotating GREASE
31
+ - browserleaks.com (canvas / webgl / webrtc / javascript) — all pass
32
+ - fingerprint.com/demo — passes
33
+ - pixelscan.net — passes
34
+ - browserscan.net — passes
35
+
36
+ **Real-target scoreboard:**
37
+ - nowsecure.nl (Cloudflare bot-fight test, nodriver author's public benchmark) — passes without challenge
38
+ - antoinevastel.com/bots (ex-DataDome VP of Research) — loads without block
39
+ - Anthem.com (Okta MFA, Angular SPA, real insurance claim submission) — end-to-end flow successful
40
+
41
+ ---
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ npm install @rester159/blacktip
47
+ npx patchright install chrome # install the Chromium backend patchright uses
48
+ ```
49
+
50
+ Or pin to a local checkout during development:
51
+
52
+ ```json
53
+ {
54
+ "dependencies": {
55
+ "@rester159/blacktip": "link:../blacktip"
56
+ }
57
+ }
58
+ ```
59
+
60
+ Run `npm install` once, then any change you make to BlackTip's compiled `dist/` is instantly visible to the consuming app (see *Local development* below).
61
+
62
+ ---
63
+
64
+ ## Quick start
65
+
66
+ ```typescript
67
+ import { BlackTip } from '@rester159/blacktip';
68
+
69
+ const bt = new BlackTip({
70
+ logLevel: 'info',
71
+ timeout: 10_000,
72
+ retryAttempts: 2,
73
+ deviceProfile: 'desktop-windows',
74
+ behaviorProfile: 'human',
75
+ });
76
+
77
+ await bt.launch();
78
+ await bt.navigate('https://example.com');
79
+ await bt.waitForStable();
80
+ await bt.type('input[name="email"]', 'you@example.com', { paste: true });
81
+ await bt.click('button[type="submit"]'); // importance auto-detected as high
82
+ await bt.waitForText('Welcome');
83
+ await bt.close();
84
+ ```
85
+
86
+ ## Agent mode (recommended)
87
+
88
+ For LLM-driven flows, use the TCP serve mode. Start the server once and send commands to it. Each response is a JSON bundle with the result, current URL, page title, and a base64 screenshot.
89
+
90
+ ```bash
91
+ # Terminal 1: start the server
92
+ npx blacktip serve
93
+
94
+ # Terminal 2: drive it
95
+ npx blacktip send "await bt.navigate('https://example.com')" --pretty
96
+ npx blacktip send "await bt.clickText('Sign in')" --pretty
97
+ npx blacktip send --file login.js --pretty
98
+ ```
99
+
100
+ MFA handling:
101
+
102
+ ```typescript
103
+ // Inside a command sent to the server:
104
+ const code = await bt.pauseForInput({ prompt: 'Enter SMS code', validate: /^\d{6}$/ });
105
+ await bt.type('input[name="credentials.passcode"]', code, { paste: true });
106
+ ```
107
+
108
+ When BlackTip hits `pauseForInput`, it sends a `{paused:true, pauseId, prompt}` frame to your client and waits. You relay the prompt to the user, get their answer, then resume:
109
+
110
+ ```bash
111
+ npx blacktip resume pause-1712345678-12345 "116170"
112
+ ```
113
+
114
+ See `AGENTS.md` for the full agent-facing reference, including the decision tree for common situations and the list of mistakes to avoid.
115
+
116
+ ---
117
+
118
+ ## Core API
119
+
120
+ | Method | Purpose |
121
+ |---|---|
122
+ | `bt.launch()` / `bt.close()` | Lifecycle |
123
+ | `bt.navigate(url, opts?)` | Go to URL |
124
+ | `bt.click(selector, opts?)` | Click by CSS/XPath. Auto-dismisses overlays, verifies interactive hit, applies importance hesitation. |
125
+ | `bt.clickText(text, {nth?, exact?, importance?})` | Click by visible text. Auto-detects "Submit"/"Pay"/"Confirm" as high importance. |
126
+ | `bt.clickRole(role, {name?})` | Click by ARIA role |
127
+ | `bt.type(selector, text, {paste?, importance?})` | Type into input. Uses `keyboard.type` for React/Angular compat, falls back to `fill()` if framework didn't register. |
128
+ | `bt.select(selector, value)` | Select `<option>` by value OR label |
129
+ | `bt.scroll({direction, amount})` | Scroll with natural deceleration |
130
+ | `bt.screenshot({path?})` | PNG or JPEG |
131
+ | `bt.waitForStable({networkIdleMs, domIdleMs, maxMs})` | Wait for page to settle — replaces fixed sleeps |
132
+ | `bt.waitForText(text, {timeout})` | Wait for text to appear in body innerText |
133
+ | `bt.waitFor(selector, {timeout, visible?})` | Wait for element |
134
+ | `bt.inspect(selector)` | `{exists, visible, tagName, text, attributes, boundingBox}` in one call |
135
+ | `bt.listOptions(buttonIdOrSelector)` | Enumerate Angular-style custom dropdowns |
136
+ | `bt.networkSince(ms, pattern?)` | Recent network requests filtered by URL pattern |
137
+ | `bt.didRequestFireSince(pattern, ms)` | Boolean: did a matching request fire? |
138
+ | `bt.dismissOverlays()` | Hide fixed/sticky overlays (chat widgets, cookie banners, Medallia, etc.) |
139
+ | `bt.extractText(selector, {multiple?})` | Get innerText |
140
+ | `bt.extractAttribute(selector, attr)` | Get attribute value |
141
+ | `bt.extractTable(selector)` | Parse `<table>` into row objects |
142
+ | `bt.findInShadowDom(cssSelector, {timeout?})` | Pierce open shadow roots |
143
+ | `bt.uploadFile(selector, path)` | File upload |
144
+ | `bt.download(selector, {saveTo})` | Click-to-download with metadata |
145
+ | `bt.frame(selector)` / `bt.frames()` | Iframe context (Stripe Elements, Braintree Hosted Fields) |
146
+ | `bt.getTabs()` / `bt.newTab()` / `bt.switchTab(i)` / `bt.closeTab(i?)` | Tab management |
147
+ | `bt.cookies()` / `bt.setCookies()` / `bt.clearCookies()` | Cookie jar |
148
+ | `bt.executeJS(script)` | Raw JS evaluation |
149
+ | `bt.pauseForInput({prompt, validate?, timeoutMs?})` | User-in-the-loop (MFA) |
150
+ | `bt.serve(port?)` | Start TCP command server |
151
+
152
+ Plus `SnapshotManager`, `ProxyPool`, `attachObservability`, and the calibration module — see the TypeScript types for details.
153
+
154
+ ---
155
+
156
+ ## Configuration
157
+
158
+ ```typescript
159
+ new BlackTip({
160
+ logLevel: 'info' | 'debug' | 'warn' | 'error',
161
+ timeout: 10_000,
162
+ retryAttempts: 2,
163
+ deviceProfile: 'desktop-windows' | 'desktop-macos' | 'desktop-linux',
164
+ behaviorProfile: 'human' | 'scraper' | ProfileConfig,
165
+ headless: false, // Always runs headful via channel:'chrome'
166
+ locale: 'en-US',
167
+ timezone: 'America/New_York',
168
+ screenResolution: { width: 1920, height: 1080 },
169
+ proxy: 'http://user:pass@host:port',
170
+ chromiumPath: '/custom/chrome',
171
+ });
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Local development
177
+
178
+ If you're iterating on BlackTip alongside a consuming app:
179
+
180
+ ```bash
181
+ # In the BlackTip directory — leave this running
182
+ npm run dev # alias for tsc --watch
183
+
184
+ # In the consuming app's package.json
185
+ {
186
+ "dependencies": {
187
+ "@rester159/blacktip": "link:../blacktip"
188
+ }
189
+ }
190
+
191
+ # Install once
192
+ npm install
193
+ ```
194
+
195
+ Changes to BlackTip's `.ts` files recompile to `dist/` on save, and the consuming app sees them instantly via the symlink.
196
+
197
+ ---
198
+
199
+ ## What BlackTip is NOT
200
+
201
+ - **Not an agent.** It doesn't plan or decide. A human or an LLM drives it.
202
+ - **Not a headless mode tool.** Always runs headful via real Chrome. There is no `headless: true` path that passes serious detectors.
203
+ - **Not a captcha solver.** It doesn't solve captchas. You can integrate a solver service (2captcha, CapSolver, Anti-Captcha) on top.
204
+ - **Not a scraper framework.** No URL queues, no robots.txt compliance built in, no rate limiting — those are the caller's responsibility.
205
+ - **Not a guarantee.** No stealth tool is. It passes every free detector and matches commercial tools on ~85-90% of checks, but the detection landscape updates constantly.
206
+
207
+ ---
208
+
209
+ ## Acceptable use
210
+
211
+ BlackTip is dual-use. It is appropriate for:
212
+
213
+ - **Authorized penetration testing** (under formal scope agreements)
214
+ - **Security research** (browser fingerprinting, anti-bot research, academic work)
215
+ - **Your own accounts** on services whose Terms of Service you have accepted
216
+ - **Legitimate automation** where the site's ToS permits automated access
217
+ - **Defensive work** (bot detection development — running BlackTip against your own site to see what slips through)
218
+
219
+ It is NOT appropriate for:
220
+
221
+ - Unauthorized access to any system
222
+ - Circumventing paywalls or subscription limits
223
+ - Scraping sites that explicitly forbid automation in their ToS
224
+ - Fraud, impersonation, or identity-related offenses
225
+ - Harassment or spam automation
226
+ - Any activity that would violate the Computer Fraud and Abuse Act or equivalent laws in your jurisdiction
227
+
228
+ The authors disclaim responsibility for misuse. If you're unsure whether your use case is legitimate, it probably isn't.
229
+
230
+ ---
231
+
232
+ ## License
233
+
234
+ MIT. See `LICENSE`.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Behavioral calibration — ingestion and parameter-fitting for real
3
+ * human mouse dynamics and keystroke dynamics datasets.
4
+ *
5
+ * This module is a scaffold. The actual dataset downloads (Balabit Mouse
6
+ * Dynamics Challenge, Chao Shen's mouse data, CMU Keystroke Dynamics,
7
+ * GREYC-NISLAB) are not bundled — they're free-for-research but have
8
+ * varying license terms and live on academic FTP sites that come and go.
9
+ * The structure here is designed so that when you DO have a dataset in
10
+ * hand (CSV, JSON, or the dataset's native format), you can write a tiny
11
+ * parser, feed it through `fitFromSamples`, and get back a
12
+ * `CalibratedProfile` that plugs directly into `BehavioralEngine`.
13
+ *
14
+ * The philosophy is: we don't ship training data, we ship the
15
+ * calibration pipeline. Users bring their own data, we turn it into a
16
+ * behavioral profile. This avoids license issues and lets different
17
+ * users calibrate against different populations (e.g., a bank's fraud
18
+ * team might calibrate against their own telemetry).
19
+ */
20
+ import type { ProfileConfig } from '../types.js';
21
+ /** A single mouse-movement observation: one (x, y, t) triple plus the
22
+ * source target if known. */
23
+ export interface MouseSample {
24
+ /** Time in milliseconds since the start of the movement. */
25
+ timestampMs: number;
26
+ /** Cursor position at that time. */
27
+ x: number;
28
+ y: number;
29
+ /** Optional: the target center the user was aiming at, if known. */
30
+ targetX?: number;
31
+ targetY?: number;
32
+ /** Optional: width of the target in pixels (needed for Fitts' Law fit). */
33
+ targetWidth?: number;
34
+ }
35
+ /** A complete movement — cursor samples from the start of motion to the
36
+ * click. Many datasets ship this as a sequence. */
37
+ export interface MouseMovement {
38
+ samples: MouseSample[];
39
+ /** Did the movement end with a click? Some datasets mix navigation
40
+ * moves with target clicks. */
41
+ endedWithClick: boolean;
42
+ }
43
+ /** A single keystroke observation. */
44
+ export interface KeystrokeSample {
45
+ /** The key character produced (for layout-aware fitting). */
46
+ key: string;
47
+ /** Milliseconds from the previous keystroke's down-event. */
48
+ flightTimeMs: number;
49
+ /** Milliseconds the key was held down. */
50
+ holdTimeMs: number;
51
+ }
52
+ /** A complete typing session — a sequence of keystrokes for a single
53
+ * phrase or field. */
54
+ export interface TypingSession {
55
+ keystrokes: KeystrokeSample[];
56
+ /** Original text that was typed, if known. */
57
+ phrase?: string;
58
+ }
59
+ export interface DistributionFit {
60
+ min: number;
61
+ max: number;
62
+ mean: number;
63
+ sigma: number;
64
+ p5: number;
65
+ p50: number;
66
+ p95: number;
67
+ sampleCount: number;
68
+ }
69
+ export interface MouseFit {
70
+ /** Fitts' Law intercept constant a (ms). */
71
+ fittsA: number;
72
+ /** Fitts' Law slope constant b (ms per bit of index of difficulty). */
73
+ fittsB: number;
74
+ /** Distribution of path length divided by straight-line distance
75
+ * (how much the user's path curved). Real humans ~1.05–1.15. */
76
+ pathCurvatureRatio: DistributionFit;
77
+ /** Distribution of the maximum perpendicular deviation from the
78
+ * straight line, in pixels. */
79
+ perpendicularDeviation: DistributionFit;
80
+ /** Distribution of "overshoot and correct" distances in pixels for
81
+ * targets smaller than 50px. */
82
+ overshootPx: DistributionFit;
83
+ }
84
+ export interface TypingFit {
85
+ /** Overall flight-time distribution across all keystrokes. */
86
+ flightTime: DistributionFit;
87
+ /** Hold-time distribution. */
88
+ holdTime: DistributionFit;
89
+ /** Digraph-specific flight times for common pairs (th, er, in, etc.). */
90
+ digraphFlightTime: Record<string, DistributionFit>;
91
+ /** Mistake rate (typos per character). */
92
+ mistakeRate: number;
93
+ }
94
+ /**
95
+ * A calibrated behavioral profile: raw fits plus a `ProfileConfig` that
96
+ * is derived from them and ready to pass to `new BehavioralEngine(...)`.
97
+ */
98
+ export interface CalibratedProfile {
99
+ name: string;
100
+ source: string;
101
+ sampleCount: number;
102
+ mouse: MouseFit;
103
+ typing: TypingFit;
104
+ profileConfig: ProfileConfig;
105
+ }
106
+ /**
107
+ * Fit a `DistributionFit` to a 1D array of observations.
108
+ * Uses the empirical min/max/mean/stddev and samples the empirical CDF
109
+ * for the 5/50/95 percentiles rather than assuming normality — many
110
+ * behavioral measurements are right-skewed (log-normal-ish).
111
+ */
112
+ export declare function fitDistribution(samples: readonly number[]): DistributionFit;
113
+ /**
114
+ * Fit Fitts' Law constants (a, b) from a set of mouse movements via
115
+ * ordinary least squares on (log2(D/W + 1), MT).
116
+ *
117
+ * Falls back to the canonical (a=90, b=140) if the dataset doesn't
118
+ * provide target widths.
119
+ */
120
+ export declare function fitFittsLaw(movements: readonly MouseMovement[]): {
121
+ a: number;
122
+ b: number;
123
+ };
124
+ /**
125
+ * Fit mouse dynamics from a set of recorded movements.
126
+ */
127
+ export declare function fitMouseDynamics(movements: readonly MouseMovement[]): MouseFit;
128
+ /**
129
+ * Fit typing dynamics from a set of recorded typing sessions.
130
+ */
131
+ export declare function fitTypingDynamics(sessions: readonly TypingSession[]): TypingFit;
132
+ /**
133
+ * Given fitted mouse and typing dynamics, derive a ProfileConfig that
134
+ * plugs into `new BehavioralEngine(profileConfig)`.
135
+ *
136
+ * The mapping is conservative: we use the 5th–95th percentiles from the
137
+ * fits as the `[min, max]` tuples for the engine's uniform-ish sampling.
138
+ */
139
+ export declare function deriveProfileConfig(mouse: MouseFit, typing: TypingFit): ProfileConfig;
140
+ /**
141
+ * End-to-end calibration: take raw mouse movements and typing sessions,
142
+ * produce a CalibratedProfile.
143
+ */
144
+ export declare function fitFromSamples(name: string, source: string, movements: readonly MouseMovement[], sessions: readonly TypingSession[]): CalibratedProfile;
145
+ //# sourceMappingURL=calibration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calibration.d.ts","sourceRoot":"","sources":["../../src/behavioral/calibration.ts"],"names":[],"mappings":"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"}