@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/dist/cli.js ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BlackTip CLI
4
+ *
5
+ * Server mode: npx blacktip serve [--port 9779]
6
+ * Send command: npx blacktip send "<js>" [--port 9779] [--pretty]
7
+ * Send from file: npx blacktip send --file <path> [--port 9779] [--pretty]
8
+ * Send from stdin: echo "<js>" | npx blacktip send --stdin [--pretty]
9
+ * Batch commands: npx blacktip batch <file.json> [--port 9779]
10
+ * Resume pause: npx blacktip resume <pauseId> "<value>" [--port 9779]
11
+ * Exec (one-shot): npx blacktip exec "<js>"
12
+ *
13
+ * The server returns a JSON bundle for each command:
14
+ * { ok, result?, url?, title?, screenshotPath?, screenshotB64?, durationMs, error? }
15
+ *
16
+ * --pretty formats the bundle but omits the screenshot payload so the
17
+ * console output is readable. The screenshot is still saved to disk.
18
+ */
19
+ import { createConnection } from 'node:net';
20
+ import { readFileSync } from 'node:fs';
21
+ import { BlackTip } from './blacktip.js';
22
+ const args = process.argv.slice(2);
23
+ const command = args[0];
24
+ const portIdx = args.indexOf('--port');
25
+ const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 9779;
26
+ const pretty = args.includes('--pretty');
27
+ function argValue(flag) {
28
+ const idx = args.indexOf(flag);
29
+ return idx !== -1 ? args[idx + 1] : undefined;
30
+ }
31
+ const DELIM = '\n__END__\n';
32
+ /**
33
+ * Send a raw payload to the server and print the response(s).
34
+ * Returns when the connection closes or the final frame is received.
35
+ */
36
+ async function sendAndPrint(payload, expectMultiple = false) {
37
+ return new Promise((resolve) => {
38
+ const client = createConnection({ port, host: '127.0.0.1' });
39
+ let buf = '';
40
+ let lastPrinted = false;
41
+ client.on('data', (chunk) => {
42
+ buf += chunk.toString();
43
+ while (buf.includes(DELIM)) {
44
+ const idx = buf.indexOf(DELIM);
45
+ const frame = buf.slice(0, idx);
46
+ buf = buf.slice(idx + DELIM.length);
47
+ printFrame(frame);
48
+ lastPrinted = true;
49
+ if (!expectMultiple) {
50
+ client.destroy();
51
+ resolve();
52
+ return;
53
+ }
54
+ }
55
+ });
56
+ client.on('close', () => {
57
+ if (!lastPrinted && buf.length > 0)
58
+ printFrame(buf);
59
+ resolve();
60
+ });
61
+ client.on('error', () => {
62
+ console.error('Server not running. Start with: npx blacktip serve');
63
+ process.exit(1);
64
+ });
65
+ client.write(payload + DELIM);
66
+ });
67
+ }
68
+ function printFrame(frame) {
69
+ // Try to parse as JSON; if it parses, pretty-print (optionally
70
+ // stripping the huge base64 screenshot). Fall back to raw output.
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(frame);
74
+ }
75
+ catch {
76
+ process.stdout.write(frame + '\n');
77
+ return;
78
+ }
79
+ if (pretty && parsed && typeof parsed === 'object') {
80
+ const clone = { ...parsed };
81
+ if ('screenshotB64' in clone) {
82
+ const bytes = typeof clone.screenshotBytes === 'number' ? clone.screenshotBytes : clone.screenshotB64.length;
83
+ clone.screenshotB64 = `<${bytes} bytes, saved to ${clone.screenshotPath ?? 'disk'}>`;
84
+ }
85
+ process.stdout.write(JSON.stringify(clone, null, 2) + '\n');
86
+ }
87
+ else {
88
+ process.stdout.write(JSON.stringify(parsed) + '\n');
89
+ }
90
+ }
91
+ if (command === 'serve') {
92
+ const bt = new BlackTip({
93
+ headless: false,
94
+ logLevel: 'info',
95
+ deviceProfile: 'desktop-windows',
96
+ behaviorProfile: 'scraper',
97
+ timeout: 10000,
98
+ retryAttempts: 2,
99
+ screenResolution: { width: 1440, height: 900 },
100
+ });
101
+ bt.on('action', (e) => {
102
+ console.log(`[${e.action}] ${e.target} → ${e.outcome} (${e.duration}ms)`);
103
+ });
104
+ await bt.serve(port);
105
+ console.log(`BlackTip server ready on port ${port}`);
106
+ console.log(`Send commands: npx blacktip send "await bt.navigate('...')" --port ${port}`);
107
+ }
108
+ else if (command === 'send') {
109
+ // Three input sources:
110
+ // 1. Positional argument: npx blacktip send "..."
111
+ // 2. --file <path>: npx blacktip send --file cmd.js
112
+ // 3. --stdin: echo "..." | npx blacktip send --stdin
113
+ let cmd;
114
+ const fileArg = argValue('--file');
115
+ const stdinFlag = args.includes('--stdin');
116
+ if (fileArg) {
117
+ cmd = readFileSync(fileArg, 'utf-8');
118
+ }
119
+ else if (stdinFlag) {
120
+ cmd = readFileSync(0, 'utf-8'); // read from stdin
121
+ }
122
+ else {
123
+ // Positional argument (everything after "send" that isn't a flag).
124
+ cmd = args.slice(1)
125
+ .filter((a, i, arr) => {
126
+ if (a === '--port' || a === '--file' || a === '--stdin' || a === '--pretty')
127
+ return false;
128
+ if (i > 0 && (arr[i - 1] === '--port' || arr[i - 1] === '--file'))
129
+ return false;
130
+ return true;
131
+ })
132
+ .join(' ');
133
+ }
134
+ if (!cmd || !cmd.trim()) {
135
+ console.error('Usage: npx blacktip send "<js>" | --file <path> | --stdin');
136
+ process.exit(1);
137
+ }
138
+ await sendAndPrint(cmd);
139
+ }
140
+ else if (command === 'batch') {
141
+ // Batch mode: read a JSON array of commands from a file and send them
142
+ // as a single BATCH request. Server runs them sequentially.
143
+ const filePath = args[1];
144
+ if (!filePath) {
145
+ console.error('Usage: npx blacktip batch <file.json>');
146
+ process.exit(1);
147
+ }
148
+ const raw = readFileSync(filePath, 'utf-8');
149
+ const parsed = JSON.parse(raw);
150
+ if (!Array.isArray(parsed)) {
151
+ console.error('batch file must contain a JSON array of command strings');
152
+ process.exit(1);
153
+ }
154
+ await sendAndPrint('BATCH\n' + JSON.stringify(parsed));
155
+ }
156
+ else if (command === 'resume') {
157
+ // Resume a paused command: npx blacktip resume <id> "<value>"
158
+ const id = args[1];
159
+ const value = args[2] ?? '';
160
+ if (!id) {
161
+ console.error('Usage: npx blacktip resume <pauseId> "<value>"');
162
+ process.exit(1);
163
+ }
164
+ await sendAndPrint(`RESUME ${id}\n${value}`);
165
+ }
166
+ else if (command === 'pending') {
167
+ // List currently-paused commands: npx blacktip pending
168
+ await sendAndPrint('LIST_PENDING');
169
+ }
170
+ else if (command === 'exec') {
171
+ // One-shot: launch browser, run command, close.
172
+ const cmd = args.slice(1).filter((a) => a !== '--port' && a !== String(port)).join(' ');
173
+ if (!cmd) {
174
+ console.error('Usage: npx blacktip exec "<js>"');
175
+ process.exit(1);
176
+ }
177
+ const bt = new BlackTip({ headless: false, timeout: 10000, retryAttempts: 2 });
178
+ await bt.launch();
179
+ try {
180
+ const fn = new Function('bt', `return (async () => { ${cmd} })();`);
181
+ const result = await fn(bt);
182
+ if (result !== undefined)
183
+ console.log(JSON.stringify(result));
184
+ }
185
+ catch (e) {
186
+ console.error('Error:', e instanceof Error ? e.message : e);
187
+ }
188
+ await bt.close();
189
+ }
190
+ else {
191
+ console.log(`BlackTip CLI
192
+
193
+ Usage:
194
+ npx blacktip serve [--port 9779]
195
+ Start the TCP command server. Leaves a browser running.
196
+
197
+ npx blacktip send "<js>" [--port N] [--pretty]
198
+ npx blacktip send --file <path> [--pretty]
199
+ echo "<js>" | npx blacktip send --stdin [--pretty]
200
+ Send a single JS command to the running server. Returns a bundle:
201
+ { ok, result, url, title, screenshotPath, durationMs, ... }
202
+ Use --pretty for human-readable output with screenshot payload
203
+ replaced by a placeholder.
204
+
205
+ npx blacktip batch <file.json>
206
+ Run an array of commands sequentially. Server returns all bundles
207
+ in one response, stopping on first failure.
208
+
209
+ npx blacktip resume <pauseId> "<value>"
210
+ Resume a command that called bt.pauseForInput().
211
+
212
+ npx blacktip pending
213
+ List currently-paused commands with their prompts.
214
+
215
+ npx blacktip exec "<js>"
216
+ One-shot: launch a fresh browser, run the command, close.
217
+
218
+ Examples:
219
+ npx blacktip serve
220
+ npx blacktip send "await bt.navigate('https://example.com')" --pretty
221
+ npx blacktip send --file login-flow.js --pretty
222
+ npx blacktip batch anthem-claim.json
223
+ npx blacktip resume pause-123456-78901 "116170"
224
+ `);
225
+ }
226
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACvC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAEzC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,KAAK,GAAG,aAAa,CAAC;AAE5B;;;GAGG;AACH,KAAK,UAAU,YAAY,CAAC,OAAe,EAAE,cAAc,GAAG,KAAK;IACjE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7D,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,WAAW,GAAG,KAAK,CAAC;QAExB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,GAAG,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACxB,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAChC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;gBACpC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAClB,WAAW,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,cAAc,EAAE,CAAC;oBACpB,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,OAAO,EAAE,CAAC;oBACV,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,UAAU,CAAC,GAAG,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;YACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,+DAA+D;IAC/D,kEAAkE;IAClE,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IAED,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,EAAE,GAAI,MAAkC,EAAE,CAAC;QACzD,IAAI,eAAe,IAAI,KAAK,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAE,KAAK,CAAC,aAAwB,CAAC,MAAM,CAAC;YACzH,KAAK,CAAC,aAAa,GAAG,IAAI,KAAK,oBAAoB,KAAK,CAAC,cAAc,IAAI,MAAM,GAAG,CAAC;QACvF,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IACxB,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC;QACtB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,MAAM;QAChB,aAAa,EAAE,iBAAiB;QAChC,eAAe,EAAE,SAAS;QAC1B,OAAO,EAAE,KAAK;QACd,aAAa,EAAE,CAAC;QAChB,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;KAC/C,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,sEAAsE,IAAI,EAAE,CAAC,CAAC;AAE5F,CAAC;KAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IAC9B,uBAAuB;IACvB,sDAAsD;IACtD,+DAA+D;IAC/D,sEAAsE;IACtE,IAAI,GAAuB,CAAC;IAE5B,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAE3C,IAAI,OAAO,EAAE,CAAC;QACZ,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,SAAS,EAAE,CAAC;QACrB,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB;IACpD,CAAC;SAAM,CAAC;QACN,mEAAmE;QACnE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;YACpB,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,UAAU;gBAAE,OAAO,KAAK,CAAC;YAC1F,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChF,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;aACD,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;AAE1B,CAAC;KAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IAC/B,sEAAsE;IACtE,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,YAAY,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAEzD,CAAC;KAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;IAChC,8DAA8D;IAC9D,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,YAAY,CAAC,UAAU,EAAE,KAAK,KAAK,EAAE,CAAC,CAAC;AAE/C,CAAC;KAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;IACjC,uDAAuD;IACvD,MAAM,YAAY,CAAC,cAAc,CAAC,CAAC;AAErC,CAAC;KAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IAC9B,gDAAgD;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/E,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,yBAAyB,GAAG,QAAQ,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;AAEnB,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCb,CAAC,CAAC;AACH,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Page, Frame, ElementHandle as PlaywrightElement } from 'patchright';
2
+ import type { Logger } from './logging.js';
3
+ import type { BoundingBox } from './types.js';
4
+ export declare class ElementFinder {
5
+ private logger;
6
+ constructor(logger: Logger);
7
+ find(pageOrFrame: Page | Frame, selector: string, options?: {
8
+ timeout?: number;
9
+ visible?: boolean;
10
+ }): Promise<PlaywrightElement>;
11
+ findInFrames(page: Page, selector: string, options?: {
12
+ timeout?: number;
13
+ visible?: boolean;
14
+ }): Promise<{
15
+ element: PlaywrightElement;
16
+ frame: Page | Frame;
17
+ }>;
18
+ private looksLikeSelector;
19
+ private looksLikeXPath;
20
+ getBoundingBox(element: PlaywrightElement): Promise<BoundingBox | null>;
21
+ /**
22
+ * Find an element inside any open shadow root reachable from the document.
23
+ *
24
+ * Recursively walks the DOM and each encountered `shadowRoot`, testing
25
+ * CSS selector matches at every level. Modern component libraries
26
+ * (Lit, Stencil, Material Web Components, Ionic) wrap everything in
27
+ * shadow DOM, and Playwright's default CSS engine sometimes misses
28
+ * deeply-nested selectors. This walker is explicit and predictable.
29
+ *
30
+ * LIMITATION: closed shadow roots are opaque to JavaScript by design.
31
+ * Elements inside `attachShadow({mode: 'closed'})` are not reachable
32
+ * by any JS-only method; reaching them requires CDP `DOM.describeNode`
33
+ * with `pierce: true`, which is outside this method's scope. Open
34
+ * shadow roots (the overwhelming majority in practice) are fully
35
+ * supported.
36
+ */
37
+ findInShadowDom(pageOrFrame: Page | Frame, cssSelector: string, options?: {
38
+ timeout?: number;
39
+ }): Promise<PlaywrightElement>;
40
+ private notFound;
41
+ }
42
+ //# sourceMappingURL=element-finder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-finder.d.ts","sourceRoot":"","sources":["../src/element-finder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8B9C,qBAAa,aAAa;IACZ,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE5B,IAAI,CACR,WAAW,EAAE,IAAI,GAAG,KAAK,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC,iBAAiB,CAAC;IA2GvB,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,EAAE,IAAI,GAAG,KAAK,CAAA;KAAE,CAAC;IAyB/D,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAKhB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAM7E;;;;;;;;;;;;;;;OAeG;IACG,eAAe,CACnB,WAAW,EAAE,IAAI,GAAG,KAAK,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC,iBAAiB,CAAC;IA0C7B,OAAO,CAAC,QAAQ;CAGjB"}
@@ -0,0 +1,240 @@
1
+ const CSS_TAG_NAMES = new Set([
2
+ // Structural
3
+ 'div', 'span', 'section', 'article', 'aside', 'nav', 'header', 'footer',
4
+ 'main', 'body', 'html', 'details', 'summary', 'dialog',
5
+ // Text
6
+ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code',
7
+ 'em', 'strong', 'small', 'mark', 'time',
8
+ // Lists & tables
9
+ 'ul', 'ol', 'li', 'dl', 'dt', 'dd',
10
+ 'table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'caption', 'colgroup', 'col',
11
+ // Forms
12
+ 'form', 'input', 'textarea', 'select', 'option', 'optgroup', 'button', 'label',
13
+ 'fieldset', 'legend', 'datalist', 'output', 'meter', 'progress',
14
+ // Media & embeds
15
+ 'img', 'picture', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed',
16
+ 'object', 'source', 'track', 'map', 'area',
17
+ // Interactive
18
+ 'a',
19
+ ]);
20
+ const CSS_INDICATOR_CHARS = ['.', '#', '[', '>', ':', '+', '~', '=', '*'];
21
+ /**
22
+ * Per-strategy timeout — short so we fail fast and try the next strategy.
23
+ * The overall timeout is the caller's budget; individual strategies get a slice.
24
+ */
25
+ const STRATEGY_TIMEOUT_MS = 3000;
26
+ export class ElementFinder {
27
+ logger;
28
+ constructor(logger) {
29
+ this.logger = logger;
30
+ }
31
+ async find(pageOrFrame, selector, options) {
32
+ const overallTimeout = options?.timeout ?? 15_000;
33
+ const visible = options?.visible ?? false;
34
+ const state = visible ? 'visible' : 'attached';
35
+ const triedStrategies = [];
36
+ const deadline = Date.now() + overallTimeout;
37
+ // Helper: time remaining, capped to per-strategy max
38
+ const strategyTimeout = () => Math.min(STRATEGY_TIMEOUT_MS, Math.max(500, deadline - Date.now()));
39
+ // 1. CSS selector — if input looks like one
40
+ if (this.looksLikeSelector(selector)) {
41
+ try {
42
+ const el = await pageOrFrame.waitForSelector(selector, { timeout: strategyTimeout(), state });
43
+ if (el) {
44
+ this.logger.info('Element found via CSS selector', { selector });
45
+ return el;
46
+ }
47
+ }
48
+ catch {
49
+ triedStrategies.push('CSS');
50
+ this.logger.debug('CSS strategy failed', { selector });
51
+ }
52
+ if (Date.now() >= deadline)
53
+ throw this.notFound(selector, triedStrategies);
54
+ }
55
+ // 2. XPath
56
+ if (this.looksLikeXPath(selector)) {
57
+ try {
58
+ const el = await pageOrFrame.waitForSelector(`xpath=${selector}`, { timeout: strategyTimeout(), state });
59
+ if (el) {
60
+ this.logger.info('Element found via XPath', { selector });
61
+ return el;
62
+ }
63
+ }
64
+ catch {
65
+ triedStrategies.push('XPath');
66
+ this.logger.debug('XPath strategy failed', { selector });
67
+ }
68
+ if (Date.now() >= deadline)
69
+ throw this.notFound(selector, triedStrategies);
70
+ }
71
+ // 3. Label association — runs BEFORE text match so that searching by
72
+ // a label's visible text returns the associated input, not the label
73
+ // element itself. Uses Playwright's getByLabel which handles for=,
74
+ // nested inputs, and aria-labelledby uniformly.
75
+ try {
76
+ const locator = pageOrFrame.getByLabel(selector, { exact: true }).first();
77
+ await locator.waitFor({ state, timeout: strategyTimeout() });
78
+ const handle = await locator.elementHandle();
79
+ if (handle) {
80
+ this.logger.info('Element found via label association', { selector });
81
+ return handle;
82
+ }
83
+ }
84
+ catch {
85
+ triedStrategies.push('label');
86
+ }
87
+ if (Date.now() >= deadline)
88
+ throw this.notFound(selector, triedStrategies);
89
+ // 4. Exact text match
90
+ try {
91
+ const el = await pageOrFrame.waitForSelector(`text="${selector}"`, { timeout: strategyTimeout(), state });
92
+ if (el) {
93
+ this.logger.info('Element found via exact text', { selector });
94
+ return el;
95
+ }
96
+ }
97
+ catch {
98
+ triedStrategies.push('exact text');
99
+ }
100
+ if (Date.now() >= deadline)
101
+ throw this.notFound(selector, triedStrategies);
102
+ // 5. Case-insensitive text match
103
+ try {
104
+ const el = await pageOrFrame.waitForSelector(`text=${selector}`, { timeout: strategyTimeout(), state });
105
+ if (el) {
106
+ this.logger.info('Element found via text (case-insensitive)', { selector });
107
+ return el;
108
+ }
109
+ }
110
+ catch {
111
+ triedStrategies.push('text (case-insensitive)');
112
+ }
113
+ if (Date.now() >= deadline)
114
+ throw this.notFound(selector, triedStrategies);
115
+ // 6. ARIA label
116
+ try {
117
+ const el = await pageOrFrame.waitForSelector(`[aria-label="${selector}"]`, { timeout: strategyTimeout(), state });
118
+ if (el) {
119
+ this.logger.info('Element found via ARIA label', { selector });
120
+ return el;
121
+ }
122
+ }
123
+ catch {
124
+ triedStrategies.push('ARIA label');
125
+ }
126
+ if (Date.now() >= deadline)
127
+ throw this.notFound(selector, triedStrategies);
128
+ // 7. ARIA role (button)
129
+ try {
130
+ const el = await pageOrFrame.waitForSelector(`role=button[name="${selector}"]`, { timeout: strategyTimeout(), state });
131
+ if (el) {
132
+ this.logger.info('Element found via ARIA role', { selector });
133
+ return el;
134
+ }
135
+ }
136
+ catch {
137
+ triedStrategies.push('ARIA role');
138
+ }
139
+ throw this.notFound(selector, triedStrategies);
140
+ }
141
+ async findInFrames(page, selector, options) {
142
+ try {
143
+ const element = await this.find(page, selector, options);
144
+ return { element, frame: page };
145
+ }
146
+ catch {
147
+ this.logger.debug('Not found on main page, searching iframes...', { selector });
148
+ }
149
+ const frames = page.frames();
150
+ for (const frame of frames) {
151
+ if (frame === page.mainFrame())
152
+ continue;
153
+ try {
154
+ const element = await this.find(frame, selector, { ...options, timeout: 3000 });
155
+ this.logger.info('Element found in iframe', { selector, frameUrl: frame.url() });
156
+ return { element, frame };
157
+ }
158
+ catch {
159
+ // continue to next iframe
160
+ }
161
+ }
162
+ throw new Error(`Element not found in any frame: "${selector}". Searched main page and ${frames.length - 1} iframe(s).`);
163
+ }
164
+ looksLikeSelector(input) {
165
+ const trimmed = input.trim();
166
+ if (!trimmed)
167
+ return false;
168
+ if (CSS_INDICATOR_CHARS.some((ch) => trimmed.includes(ch)))
169
+ return true;
170
+ const firstWord = trimmed.split(/[\s.[#:>+~]/, 1)[0].toLowerCase();
171
+ return CSS_TAG_NAMES.has(firstWord);
172
+ }
173
+ looksLikeXPath(input) {
174
+ const trimmed = input.trim();
175
+ return trimmed.startsWith('/') || trimmed.startsWith('//');
176
+ }
177
+ async getBoundingBox(element) {
178
+ await element.scrollIntoViewIfNeeded().catch(() => { });
179
+ const box = await element.boundingBox();
180
+ return box ? { x: box.x, y: box.y, width: box.width, height: box.height } : null;
181
+ }
182
+ /**
183
+ * Find an element inside any open shadow root reachable from the document.
184
+ *
185
+ * Recursively walks the DOM and each encountered `shadowRoot`, testing
186
+ * CSS selector matches at every level. Modern component libraries
187
+ * (Lit, Stencil, Material Web Components, Ionic) wrap everything in
188
+ * shadow DOM, and Playwright's default CSS engine sometimes misses
189
+ * deeply-nested selectors. This walker is explicit and predictable.
190
+ *
191
+ * LIMITATION: closed shadow roots are opaque to JavaScript by design.
192
+ * Elements inside `attachShadow({mode: 'closed'})` are not reachable
193
+ * by any JS-only method; reaching them requires CDP `DOM.describeNode`
194
+ * with `pierce: true`, which is outside this method's scope. Open
195
+ * shadow roots (the overwhelming majority in practice) are fully
196
+ * supported.
197
+ */
198
+ async findInShadowDom(pageOrFrame, cssSelector, options) {
199
+ const timeout = options?.timeout ?? 5000;
200
+ const deadline = Date.now() + timeout;
201
+ // Poll until the element appears or the deadline passes. The walker
202
+ // runs in browser context via evaluateHandle; we pass the function
203
+ // body as a string so TypeScript doesn't typecheck it against Node
204
+ // globals (the DOM lib isn't in tsconfig).
205
+ const walkerScript = `((selector) => {
206
+ function walk(root) {
207
+ if (!root) return null;
208
+ if (root.matches && root.matches(selector)) return root;
209
+ if (typeof root.querySelector === 'function') {
210
+ const direct = root.querySelector(selector);
211
+ if (direct) return direct;
212
+ }
213
+ const all = typeof root.querySelectorAll === 'function' ? root.querySelectorAll('*') : [];
214
+ for (const el of all) {
215
+ if (el.shadowRoot) {
216
+ const found = walk(el.shadowRoot);
217
+ if (found) return found;
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ return walk(document);
223
+ })(${JSON.stringify(cssSelector)})`;
224
+ while (Date.now() < deadline) {
225
+ const handle = await pageOrFrame.evaluateHandle(walkerScript);
226
+ const element = handle.asElement();
227
+ if (element) {
228
+ this.logger.info('Element found via shadow DOM walker', { cssSelector });
229
+ return element;
230
+ }
231
+ await handle.dispose();
232
+ await new Promise((resolve) => setTimeout(resolve, 80));
233
+ }
234
+ throw new Error(`Element not found in shadow DOM: "${cssSelector}"`);
235
+ }
236
+ notFound(selector, tried) {
237
+ return new Error(`Element not found: "${selector}". Tried: ${tried.join(', ')}`);
238
+ }
239
+ }
240
+ //# sourceMappingURL=element-finder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-finder.js","sourceRoot":"","sources":["../src/element-finder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { DeviceProfile } from './types';
2
+ /**
3
+ * Generate all evasion scripts for a given device profile.
4
+ * Each script is a self-contained IIFE ready to be injected via
5
+ * `Page.addScriptToEvaluateOnNewDocument()`.
6
+ */
7
+ /**
8
+ * Evasion script generator. With BlackTip v2 running on real Chrome
9
+ * (`channel: 'chrome'`) + patchright's CDP-level stealth patches, most of
10
+ * the original JS-shim evasion is unnecessary and some of it was actively
11
+ * creating signals (see L012 in planning/lessons.md for the chrome.runtime
12
+ * and launch-flag story).
13
+ *
14
+ * What we keep:
15
+ * - canvasNoise: privacy/anti-tracking, adds plausible noise to canvas
16
+ * rendering so we don't get a stable device-wide fingerprint.
17
+ * - audioContextOverride: same story for audio fingerprint.
18
+ *
19
+ * What we removed (and why):
20
+ * - chromeRuntime: real Chrome doesn't expose `chrome.runtime` on regular
21
+ * pages — only on extension-accessible contexts. Our shim was adding
22
+ * it everywhere, which CreepJS catches as `hasBadChromeRuntime: true`.
23
+ * Let patchright + real Chrome handle `window.chrome` naturally.
24
+ * - navigatorOverrides: real Chrome's navigator is already correct when
25
+ * we use `channel: 'chrome'`. Overriding with profile values creates
26
+ * mismatches (e.g., profile says hardwareConcurrency=8 but the actual
27
+ * CPU has 16, and other APIs like Performance.now timing can reveal
28
+ * the truth).
29
+ * - pluginsAndMimeTypes: real Chrome populates navigator.plugins with
30
+ * the real PDF viewer plugin. Our shim was adding fake plugins that
31
+ * didn't match.
32
+ * - permissionsOverride: real Chrome already returns 'prompt' for
33
+ * notifications on most states. Shim only needed on headless Chromium.
34
+ * - webglOverride: real Chrome reports the real GPU through ANGLE. Our
35
+ * shim was replacing AMD Radeon with a profile string, making the
36
+ * fingerprint inconsistent with the real DirectX pipeline signals.
37
+ */
38
+ export declare function generateEvasionScripts(profile: DeviceProfile): string[];
39
+ //# sourceMappingURL=evasion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evasion.d.ts","sourceRoot":"","sources":["../src/evasion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAmd7C;;;;GAIG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,EAAE,CAKvE"}