rensan-browser 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.
- package/README.md +133 -0
- package/dist/ai.d.ts +21 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +87 -0
- package/dist/ai.js.map +1 -0
- package/dist/browser.d.ts +59 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +97 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +23 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +103 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/session.d.ts +94 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +244 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# rensan-browser
|
|
2
|
+
|
|
3
|
+
AI-powered browser for agents — navigate, click, type, read, see images, capture requests.
|
|
4
|
+
|
|
5
|
+
Built on [Playwright](https://playwright.dev) + [Claude](https://anthropic.com). Designed for AI agents that need to interact with websites and intercept their API traffic.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install rensan-browser playwright
|
|
11
|
+
npx playwright install chromium
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { RensanBrowser } from 'rensan-browser'
|
|
18
|
+
|
|
19
|
+
const browser = new RensanBrowser({ apiKey: 'sk-ant-...' })
|
|
20
|
+
|
|
21
|
+
const session = await browser.open('https://example.com')
|
|
22
|
+
|
|
23
|
+
// Read text from the page
|
|
24
|
+
const headline = await session.read('the main headline')
|
|
25
|
+
|
|
26
|
+
// Click anything — AI finds it
|
|
27
|
+
await session.click('the Accept cookies button')
|
|
28
|
+
|
|
29
|
+
// Fill any input — AI finds it
|
|
30
|
+
await session.fill('Buenos Aires', 'the city search input')
|
|
31
|
+
|
|
32
|
+
// Take a screenshot (base64 JPEG)
|
|
33
|
+
const img = await session.screenshot()
|
|
34
|
+
|
|
35
|
+
// Capture JSON requests fired by the page
|
|
36
|
+
const requests = await session.capture(async () => {
|
|
37
|
+
await session.fill('BUE', 'origin airport')
|
|
38
|
+
await session.click('search flights button')
|
|
39
|
+
})
|
|
40
|
+
// requests = [{ url, data, score }, ...]
|
|
41
|
+
|
|
42
|
+
await session.close()
|
|
43
|
+
await browser.close()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### `new RensanBrowser(config)`
|
|
49
|
+
|
|
50
|
+
| Option | Type | Default | Description |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| `apiKey` | `string` | required | Anthropic API key |
|
|
53
|
+
| `model` | `string` | `claude-haiku-4-5` | Vision model for AI decisions |
|
|
54
|
+
| `headless` | `boolean` | `true` | Run browser headless |
|
|
55
|
+
| `timeout` | `number` | `25000` | Default navigation timeout (ms) |
|
|
56
|
+
|
|
57
|
+
### Browser methods
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
browser.open(url, options?) // open session and navigate
|
|
61
|
+
browser.newSession() // open blank session
|
|
62
|
+
browser.openMany(urls[], options?) // multiple sessions in parallel
|
|
63
|
+
browser.run(async (session) => ...) // auto-closing session
|
|
64
|
+
browser.close() // close browser
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Session methods
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Navigation
|
|
71
|
+
session.goto(url, options?)
|
|
72
|
+
|
|
73
|
+
// Interaction — AI finds the element via vision
|
|
74
|
+
session.click(goal) // 'the login button'
|
|
75
|
+
session.fill(value, goal, options?) // ('BUE', 'origin airport input')
|
|
76
|
+
session.type(text, options?) // type at current focus
|
|
77
|
+
session.moveTo(goal | { x, y }) // move cursor
|
|
78
|
+
|
|
79
|
+
// Reading
|
|
80
|
+
session.read(goal?) // 'the product price' or all text
|
|
81
|
+
session.screenshot() // base64 JPEG
|
|
82
|
+
|
|
83
|
+
// Scrolling
|
|
84
|
+
session.scroll('down' | 'up', options?)
|
|
85
|
+
|
|
86
|
+
// Request capture
|
|
87
|
+
session.capture(async () => { ... }) // returns CapturedRequest[]
|
|
88
|
+
session.startCapture()
|
|
89
|
+
session.stopCapture()
|
|
90
|
+
session.capturedRequests
|
|
91
|
+
|
|
92
|
+
// Lifecycle
|
|
93
|
+
session.wait(ms)
|
|
94
|
+
session.close()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Parallel sessions
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const [s1, s2, s3] = await browser.openMany([
|
|
101
|
+
'https://skyscanner.com',
|
|
102
|
+
'https://kayak.com',
|
|
103
|
+
'https://despegar.com',
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
const results = await Promise.all([
|
|
107
|
+
s1.capture(async () => s1.fill('BUE', 'origin')),
|
|
108
|
+
s2.capture(async () => s2.fill('BUE', 'origin')),
|
|
109
|
+
s3.capture(async () => s3.fill('BUE', 'origin')),
|
|
110
|
+
])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Request scoring
|
|
114
|
+
|
|
115
|
+
Captured requests are automatically ranked by relevance — first-party API calls score higher than tracking, ads, and analytics. No hardcoded domain lists.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const requests = await session.capture(async () => {
|
|
119
|
+
await session.goto('https://news-site.com')
|
|
120
|
+
})
|
|
121
|
+
// requests[0] is the most likely real data API
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## How AI interaction works
|
|
125
|
+
|
|
126
|
+
1. **Layer 1 — Playwright semantic (free)**: tries `getByRole('searchbox')`, `getByRole('textbox')` and common patterns. Works for most standard inputs.
|
|
127
|
+
2. **Layer 2 — Claude Haiku vision (~$0.001)**: takes a screenshot, sends it to Haiku, gets back a CSS selector. Works for any page layout.
|
|
128
|
+
|
|
129
|
+
Total cost per interaction: ~$0.001 with Haiku.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai.ts
|
|
3
|
+
* All AI calls for rensan-browser.
|
|
4
|
+
* Haiku sees the page screenshot and decides what to do.
|
|
5
|
+
*/
|
|
6
|
+
import type { AIDecision } from './types';
|
|
7
|
+
export declare class AIClient {
|
|
8
|
+
readonly model: string;
|
|
9
|
+
private client;
|
|
10
|
+
constructor(apiKey: string, model: string);
|
|
11
|
+
/**
|
|
12
|
+
* Look at a screenshot and decide what element to interact with.
|
|
13
|
+
* Returns a CSS/semantic selector and action type.
|
|
14
|
+
*/
|
|
15
|
+
decide(screenshot: string, goal: string, value?: string): Promise<AIDecision>;
|
|
16
|
+
/**
|
|
17
|
+
* Read and extract specific text from a screenshot.
|
|
18
|
+
*/
|
|
19
|
+
read(screenshot: string, goal?: string): Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=ai.d.ts.map
|
package/dist/ai.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,qBAAa,QAAQ;IAGS,QAAQ,CAAC,KAAK,EAAE,MAAM;IAFlD,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,EAAE,MAAM,EAAW,KAAK,EAAE,MAAM;IAIlD;;;OAGG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA4CnF;;OAEG;IACG,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAsB/D"}
|
package/dist/ai.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ai.ts
|
|
4
|
+
* All AI calls for rensan-browser.
|
|
5
|
+
* Haiku sees the page screenshot and decides what to do.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.AIClient = void 0;
|
|
12
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
13
|
+
class AIClient {
|
|
14
|
+
constructor(apiKey, model) {
|
|
15
|
+
this.model = model;
|
|
16
|
+
this.client = new sdk_1.default({ apiKey });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Look at a screenshot and decide what element to interact with.
|
|
20
|
+
* Returns a CSS/semantic selector and action type.
|
|
21
|
+
*/
|
|
22
|
+
async decide(screenshot, goal, value) {
|
|
23
|
+
const valueHint = value ? `Value to type: "${value}"` : '';
|
|
24
|
+
const msg = await this.client.messages.create({
|
|
25
|
+
model: this.model,
|
|
26
|
+
max_tokens: 200,
|
|
27
|
+
messages: [{
|
|
28
|
+
role: 'user',
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'image',
|
|
32
|
+
source: { type: 'base64', media_type: 'image/jpeg', data: screenshot },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: `Goal: ${goal}
|
|
37
|
+
${valueHint}
|
|
38
|
+
|
|
39
|
+
Look at this screenshot of a webpage.
|
|
40
|
+
Find the best element to interact with to achieve the goal.
|
|
41
|
+
|
|
42
|
+
Respond ONLY with JSON:
|
|
43
|
+
{
|
|
44
|
+
"action": "click" | "fill" | "none",
|
|
45
|
+
"selector": "a valid CSS selector or Playwright locator string",
|
|
46
|
+
"confidence": 0.0-1.0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Rules:
|
|
50
|
+
- For search/input goals use "fill"
|
|
51
|
+
- For button/link goals use "click"
|
|
52
|
+
- If nothing relevant exists use "none"
|
|
53
|
+
- Prefer specific selectors: [placeholder="..."], [aria-label="..."], button:has-text("...")`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}],
|
|
57
|
+
});
|
|
58
|
+
const raw = msg.content[0].text.trim()
|
|
59
|
+
.replace(/^```json?\s*/i, '').replace(/\s*```$/i, '').trim();
|
|
60
|
+
return JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read and extract specific text from a screenshot.
|
|
64
|
+
*/
|
|
65
|
+
async read(screenshot, goal) {
|
|
66
|
+
const prompt = goal
|
|
67
|
+
? `From this webpage screenshot, extract: ${goal}. Return only the extracted text, nothing else.`
|
|
68
|
+
: 'Extract all visible main content text from this webpage. Skip navigation, ads, and footers.';
|
|
69
|
+
const msg = await this.client.messages.create({
|
|
70
|
+
model: this.model,
|
|
71
|
+
max_tokens: 1000,
|
|
72
|
+
messages: [{
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'image',
|
|
77
|
+
source: { type: 'base64', media_type: 'image/jpeg', data: screenshot },
|
|
78
|
+
},
|
|
79
|
+
{ type: 'text', text: prompt },
|
|
80
|
+
],
|
|
81
|
+
}],
|
|
82
|
+
});
|
|
83
|
+
return msg.content[0].text.trim();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.AIClient = AIClient;
|
|
87
|
+
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;;;;AAEH,4DAA0C;AAG1C,MAAa,QAAQ;IAGnB,YAAY,MAAc,EAAW,KAAa;QAAb,UAAK,GAAL,KAAK,CAAQ;QAChD,IAAI,CAAC,MAAM,GAAG,IAAI,aAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,UAAkB,EAAE,IAAY,EAAE,KAAc;QAC3D,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,mBAAmB,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAE3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,GAAG;YACf,QAAQ,EAAE,CAAC;oBACT,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,OAAO;4BACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE;yBACvE;wBACD;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,SAAS,IAAI;EAC7B,SAAS;;;;;;;;;;;;;;;;6FAgBkF;yBAClF;qBACF;iBACF,CAAC;SACH,CAAC,CAAC;QAEH,MAAM,GAAG,GAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAoC,CAAC,IAAI,CAAC,IAAI,EAAE;aACvE,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAE/D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,UAAkB,EAAE,IAAa;QAC1C,MAAM,MAAM,GAAG,IAAI;YACjB,CAAC,CAAC,0CAA0C,IAAI,iDAAiD;YACjG,CAAC,CAAC,6FAA6F,CAAC;QAElG,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,CAAC;oBACT,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,OAAO;4BACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE;yBACvE;wBACD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;qBAC/B;iBACF,CAAC;SACH,CAAC,CAAC;QAEH,OAAQ,GAAG,CAAC,OAAO,CAAC,CAAC,CAAoC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACxE,CAAC;CACF;AAhFD,4BAgFC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser.ts
|
|
3
|
+
* Main entry point. Manages Playwright browser lifecycle and creates Sessions.
|
|
4
|
+
*/
|
|
5
|
+
import { Session } from './session';
|
|
6
|
+
import type { RensanConfig, GotoOptions } from './types';
|
|
7
|
+
export declare class RensanBrowser {
|
|
8
|
+
private browser;
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: RensanConfig);
|
|
11
|
+
launch(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Open a new browser session (tab).
|
|
15
|
+
* Auto-launches browser if not running.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const session = await browser.newSession()
|
|
19
|
+
* await session.goto('https://example.com')
|
|
20
|
+
*/
|
|
21
|
+
newSession(): Promise<Session>;
|
|
22
|
+
/**
|
|
23
|
+
* Open a session and navigate to a URL in one step.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const session = await browser.open('https://example.com')
|
|
27
|
+
* await session.click('the search button')
|
|
28
|
+
*/
|
|
29
|
+
open(url: string, options?: GotoOptions): Promise<Session>;
|
|
30
|
+
/**
|
|
31
|
+
* Open multiple sessions in parallel, each navigated to its own URL.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const [s1, s2, s3] = await browser.openMany([
|
|
35
|
+
* 'https://skyscanner.com',
|
|
36
|
+
* 'https://kayak.com',
|
|
37
|
+
* 'https://despegar.com',
|
|
38
|
+
* ])
|
|
39
|
+
* const results = await Promise.all([
|
|
40
|
+
* s1.capture(async () => s1.fill('BUE', 'origin')),
|
|
41
|
+
* s2.capture(async () => s2.fill('BUE', 'origin')),
|
|
42
|
+
* s3.capture(async () => s3.fill('BUE', 'origin')),
|
|
43
|
+
* ])
|
|
44
|
+
*/
|
|
45
|
+
openMany(urls: string[], options?: GotoOptions): Promise<Session[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Run a task in a temporary session that auto-closes when done.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* const requests = await browser.run(async (session) => {
|
|
51
|
+
* await session.goto('https://example.com')
|
|
52
|
+
* return session.capture(async () => {
|
|
53
|
+
* await session.fill('argentina', 'search')
|
|
54
|
+
* })
|
|
55
|
+
* })
|
|
56
|
+
*/
|
|
57
|
+
run<T>(fn: (session: Session) => Promise<T>): Promise<T>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAQzD,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,EAAE,YAAY;IAM1B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAOpC;;;;;;OAMG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAMhE;;;;;;;;;;;;;;OAcG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAIzE;;;;;;;;;;OAUG;IACG,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQ/D"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* browser.ts
|
|
4
|
+
* Main entry point. Manages Playwright browser lifecycle and creates Sessions.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.RensanBrowser = void 0;
|
|
8
|
+
const playwright_1 = require("playwright");
|
|
9
|
+
const session_1 = require("./session");
|
|
10
|
+
const DEFAULTS = {
|
|
11
|
+
model: 'claude-haiku-4-5',
|
|
12
|
+
headless: true,
|
|
13
|
+
timeout: 25000,
|
|
14
|
+
};
|
|
15
|
+
class RensanBrowser {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.browser = null;
|
|
18
|
+
this.config = { ...DEFAULTS, ...config };
|
|
19
|
+
}
|
|
20
|
+
// ─── Lifecycle ───────────────────────────────────────────────────────────────
|
|
21
|
+
async launch() {
|
|
22
|
+
if (this.browser)
|
|
23
|
+
return;
|
|
24
|
+
this.browser = await playwright_1.chromium.launch({ headless: this.config.headless });
|
|
25
|
+
}
|
|
26
|
+
async close() {
|
|
27
|
+
await this.browser?.close().catch(() => { });
|
|
28
|
+
this.browser = null;
|
|
29
|
+
}
|
|
30
|
+
// ─── Session factory ─────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Open a new browser session (tab).
|
|
33
|
+
* Auto-launches browser if not running.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const session = await browser.newSession()
|
|
37
|
+
* await session.goto('https://example.com')
|
|
38
|
+
*/
|
|
39
|
+
async newSession() {
|
|
40
|
+
await this.launch();
|
|
41
|
+
const ctx = await this.browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
return new session_1.Session(page, this.config.apiKey, this.config.model, this.config.timeout);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Open a session and navigate to a URL in one step.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const session = await browser.open('https://example.com')
|
|
50
|
+
* await session.click('the search button')
|
|
51
|
+
*/
|
|
52
|
+
async open(url, options) {
|
|
53
|
+
const session = await this.newSession();
|
|
54
|
+
await session.goto(url, options);
|
|
55
|
+
return session;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Open multiple sessions in parallel, each navigated to its own URL.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const [s1, s2, s3] = await browser.openMany([
|
|
62
|
+
* 'https://skyscanner.com',
|
|
63
|
+
* 'https://kayak.com',
|
|
64
|
+
* 'https://despegar.com',
|
|
65
|
+
* ])
|
|
66
|
+
* const results = await Promise.all([
|
|
67
|
+
* s1.capture(async () => s1.fill('BUE', 'origin')),
|
|
68
|
+
* s2.capture(async () => s2.fill('BUE', 'origin')),
|
|
69
|
+
* s3.capture(async () => s3.fill('BUE', 'origin')),
|
|
70
|
+
* ])
|
|
71
|
+
*/
|
|
72
|
+
async openMany(urls, options) {
|
|
73
|
+
return Promise.all(urls.map(url => this.open(url, options)));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Run a task in a temporary session that auto-closes when done.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* const requests = await browser.run(async (session) => {
|
|
80
|
+
* await session.goto('https://example.com')
|
|
81
|
+
* return session.capture(async () => {
|
|
82
|
+
* await session.fill('argentina', 'search')
|
|
83
|
+
* })
|
|
84
|
+
* })
|
|
85
|
+
*/
|
|
86
|
+
async run(fn) {
|
|
87
|
+
const session = await this.newSession();
|
|
88
|
+
try {
|
|
89
|
+
return await fn(session);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await session.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.RensanBrowser = RensanBrowser;
|
|
97
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,2CAAoD;AACpD,uCAAoC;AAGpC,MAAM,QAAQ,GAAG;IACf,KAAK,EAAK,kBAAkB;IAC5B,QAAQ,EAAE,IAAI;IACd,OAAO,EAAG,KAAK;CAChB,CAAC;AAEF,MAAa,aAAa;IAIxB,YAAY,MAAoB;QAHxB,YAAO,GAAmB,IAAI,CAAC;QAIrC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3C,CAAC;IAED,gFAAgF;IAEhF,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,MAAM,qBAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,gFAAgF;IAEhF;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,GAAG,GAAI,MAAM,IAAI,CAAC,OAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACxF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,IAAI,iBAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvF,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,OAAqB;QAC3C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACxC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAc,EAAE,OAAqB;QAClD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,GAAG,CAAI,EAAoC;QAC/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AAxFD,sCAwFC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EACV,YAAY,EACZ,eAAe,EACf,WAAW,EACX,WAAW,EACX,aAAa,GACd,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Session = exports.RensanBrowser = void 0;
|
|
4
|
+
var browser_1 = require("./browser");
|
|
5
|
+
Object.defineProperty(exports, "RensanBrowser", { enumerable: true, get: function () { return browser_1.RensanBrowser; } });
|
|
6
|
+
var session_1 = require("./session");
|
|
7
|
+
Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAA0C;AAAjC,wGAAA,aAAa,OAAA;AACtB,qCAAoC;AAA3B,kGAAA,OAAO,OAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* interceptor.ts
|
|
3
|
+
* Captures JSON network responses from a Playwright page.
|
|
4
|
+
* Uses structural scoring to rank useful vs junk URLs.
|
|
5
|
+
*/
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
import type { CapturedRequest } from './types';
|
|
8
|
+
export declare class Interceptor {
|
|
9
|
+
private page;
|
|
10
|
+
private captured;
|
|
11
|
+
private handler;
|
|
12
|
+
private siteUrl;
|
|
13
|
+
constructor(page: Page, siteUrl?: string);
|
|
14
|
+
setSiteUrl(url: string): void;
|
|
15
|
+
start(): void;
|
|
16
|
+
stop(): CapturedRequest[];
|
|
17
|
+
get raw(): Array<{
|
|
18
|
+
url: string;
|
|
19
|
+
data: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
ranked(topN?: number): CapturedRequest[];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAsC/C,qBAAa,WAAW;IAKV,OAAO,CAAC,IAAI;IAJxB,OAAO,CAAC,QAAQ,CAA6C;IAC7D,OAAO,CAAC,OAAO,CAAmD;IAClE,OAAO,CAAC,OAAO,CAAS;gBAEJ,IAAI,EAAE,IAAI,EAAE,OAAO,SAAK;IAI5C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7B,KAAK,IAAI,IAAI;IAyBb,IAAI,IAAI,eAAe,EAAE;IAQzB,IAAI,GAAG,IAAI,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAE/C;IAED,MAAM,CAAC,IAAI,SAAK,GAAG,eAAe,EAAE;CAMrC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* interceptor.ts
|
|
4
|
+
* Captures JSON network responses from a Playwright page.
|
|
5
|
+
* Uses structural scoring to rank useful vs junk URLs.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.Interceptor = void 0;
|
|
9
|
+
function scoreUrl(url, siteUrl) {
|
|
10
|
+
let score = 0;
|
|
11
|
+
try {
|
|
12
|
+
const u = new URL(url);
|
|
13
|
+
const site = new URL(siteUrl);
|
|
14
|
+
const siteDomain = site.hostname.replace(/^www\./, '');
|
|
15
|
+
const urlDomain = u.hostname.replace(/^www\./, '');
|
|
16
|
+
// First-party = strong signal
|
|
17
|
+
if (urlDomain === siteDomain || urlDomain.endsWith('.' + siteDomain) || siteDomain.endsWith('.' + urlDomain)) {
|
|
18
|
+
score += 10;
|
|
19
|
+
}
|
|
20
|
+
const path = u.pathname.toLowerCase();
|
|
21
|
+
const params = [...u.searchParams];
|
|
22
|
+
if (/\/api\/|\/v\d+\/|\/rest\/|\/graphql/.test(path))
|
|
23
|
+
score += 5;
|
|
24
|
+
if (/\/content\/|\/articles\/|\/news\/|\/feed\//.test(path))
|
|
25
|
+
score += 3;
|
|
26
|
+
if (/\/search\/|\/query\/|\/data\/|\/results\//.test(path))
|
|
27
|
+
score += 3;
|
|
28
|
+
if (/\/products?\/|\/items?\/|\/catalog\//.test(path))
|
|
29
|
+
score += 2;
|
|
30
|
+
// Tracking param keys
|
|
31
|
+
const trackingKeys = ['uid', 'partner_id', 'gdpr', 'xcid', 'anId', 'tid', 'sjk', 'pid', 'coppa', 'enrich', 'sdkp'];
|
|
32
|
+
const trackingHits = params.filter(([k]) => trackingKeys.includes(k)).length;
|
|
33
|
+
score -= trackingHits * 2;
|
|
34
|
+
if (params.length > 8)
|
|
35
|
+
score -= 3;
|
|
36
|
+
if (params.length > 15)
|
|
37
|
+
score -= 5;
|
|
38
|
+
if (url.length > 300)
|
|
39
|
+
score -= 3;
|
|
40
|
+
if (url.length > 500)
|
|
41
|
+
score -= 5;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return -99;
|
|
45
|
+
}
|
|
46
|
+
return score;
|
|
47
|
+
}
|
|
48
|
+
class Interceptor {
|
|
49
|
+
constructor(page, siteUrl = '') {
|
|
50
|
+
this.page = page;
|
|
51
|
+
this.captured = [];
|
|
52
|
+
this.handler = null;
|
|
53
|
+
this.siteUrl = siteUrl;
|
|
54
|
+
}
|
|
55
|
+
setSiteUrl(url) {
|
|
56
|
+
this.siteUrl = url;
|
|
57
|
+
}
|
|
58
|
+
start() {
|
|
59
|
+
this.captured = [];
|
|
60
|
+
this.handler = async (res) => {
|
|
61
|
+
const status = res.status();
|
|
62
|
+
const ct = res.headers()['content-type'] ?? '';
|
|
63
|
+
const url = res.url();
|
|
64
|
+
if (status !== 200)
|
|
65
|
+
return;
|
|
66
|
+
if (!ct.includes('json'))
|
|
67
|
+
return;
|
|
68
|
+
if (url.length > 600)
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
if (!data || typeof data !== 'object')
|
|
73
|
+
return;
|
|
74
|
+
const keys = Array.isArray(data)
|
|
75
|
+
? Object.keys(data[0] ?? {})
|
|
76
|
+
: Object.keys(data);
|
|
77
|
+
if (keys.length < 2)
|
|
78
|
+
return;
|
|
79
|
+
this.captured.push({ url, data });
|
|
80
|
+
}
|
|
81
|
+
catch { /* skip */ }
|
|
82
|
+
};
|
|
83
|
+
this.page.on('response', this.handler);
|
|
84
|
+
}
|
|
85
|
+
stop() {
|
|
86
|
+
if (this.handler) {
|
|
87
|
+
this.page.off('response', this.handler);
|
|
88
|
+
this.handler = null;
|
|
89
|
+
}
|
|
90
|
+
return this.ranked();
|
|
91
|
+
}
|
|
92
|
+
get raw() {
|
|
93
|
+
return [...this.captured];
|
|
94
|
+
}
|
|
95
|
+
ranked(topN = 15) {
|
|
96
|
+
return this.captured
|
|
97
|
+
.map(r => ({ url: r.url, data: r.data, score: scoreUrl(r.url, this.siteUrl) }))
|
|
98
|
+
.sort((a, b) => b.score - a.score)
|
|
99
|
+
.slice(0, topN);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.Interceptor = Interceptor;
|
|
103
|
+
//# sourceMappingURL=interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session.ts
|
|
3
|
+
* One browser tab — the main object you interact with.
|
|
4
|
+
*
|
|
5
|
+
* session.goto() — navigate
|
|
6
|
+
* session.click() — AI finds and clicks element
|
|
7
|
+
* session.fill() — AI finds input and types
|
|
8
|
+
* session.type() — type at current focus
|
|
9
|
+
* session.read() — AI reads text from page
|
|
10
|
+
* session.screenshot() — returns base64 JPEG
|
|
11
|
+
* session.scroll() — scroll up/down
|
|
12
|
+
* session.moveTo() — move cursor to element or coords
|
|
13
|
+
* session.capture() — run block while capturing requests
|
|
14
|
+
*/
|
|
15
|
+
import type { Page } from 'playwright';
|
|
16
|
+
import type { CapturedRequest, GotoOptions, FillOptions, ScrollOptions } from './types';
|
|
17
|
+
export declare class Session {
|
|
18
|
+
readonly page: Page;
|
|
19
|
+
private defaultTimeout;
|
|
20
|
+
private ai;
|
|
21
|
+
private interceptor;
|
|
22
|
+
private currentUrl;
|
|
23
|
+
constructor(page: Page, apiKey: string, model: string, defaultTimeout: number);
|
|
24
|
+
goto(url: string, options?: GotoOptions): Promise<void>;
|
|
25
|
+
wait(ms: number): Promise<void>;
|
|
26
|
+
screenshot(): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Click an element described in natural language.
|
|
29
|
+
* AI sees the page and finds the right element.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* await session.click('the Accept cookies button')
|
|
33
|
+
* await session.click('the search icon in the top right')
|
|
34
|
+
*/
|
|
35
|
+
click(goal: string): Promise<boolean>;
|
|
36
|
+
private semanticClick;
|
|
37
|
+
/**
|
|
38
|
+
* Find an input and type a value.
|
|
39
|
+
* AI sees the page and locates the right field.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* await session.fill('BUE', 'the origin airport input')
|
|
43
|
+
* await session.fill('argentina', 'the main search field')
|
|
44
|
+
*/
|
|
45
|
+
fill(value: string, goal: string, options?: FillOptions): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Type text at the current focused element.
|
|
48
|
+
*/
|
|
49
|
+
type(text: string, options?: FillOptions): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Read text from the page using AI vision.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const title = await session.read('the main article headline')
|
|
55
|
+
* const price = await session.read('the product price')
|
|
56
|
+
* const all = await session.read() // all visible content
|
|
57
|
+
*/
|
|
58
|
+
read(goal?: string): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Scroll the page.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* await session.scroll('down')
|
|
64
|
+
* await session.scroll('up', { pixels: 200 })
|
|
65
|
+
* await session.scroll('down', { pixels: 800 })
|
|
66
|
+
*/
|
|
67
|
+
scroll(direction: 'up' | 'down', options?: ScrollOptions): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Move cursor to coordinates or to an element described in natural language.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* await session.moveTo({ x: 100, y: 200 })
|
|
73
|
+
* await session.moveTo('the hamburger menu icon')
|
|
74
|
+
*/
|
|
75
|
+
moveTo(target: {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
} | string): Promise<void>;
|
|
79
|
+
startCapture(): void;
|
|
80
|
+
stopCapture(): CapturedRequest[];
|
|
81
|
+
get capturedRequests(): CapturedRequest[];
|
|
82
|
+
/**
|
|
83
|
+
* Run a block and return captured + scored requests after.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const requests = await session.capture(async () => {
|
|
87
|
+
* await session.fill('BUE', 'origin airport')
|
|
88
|
+
* await session.click('search button')
|
|
89
|
+
* })
|
|
90
|
+
*/
|
|
91
|
+
capture(fn: () => Promise<void>): Promise<CapturedRequest[]>;
|
|
92
|
+
close(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExF,qBAAa,OAAO;IAMhB,QAAQ,CAAC,IAAI,EAAE,IAAI;IAGnB,OAAO,CAAC,cAAc;IARxB,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAM;gBAGb,IAAI,EAAE,IAAI,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACL,cAAc,EAAE,MAAM;IAQ1B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/B,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAOnC;;;;;;;OAOG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAkB7B,aAAa;IAyB3B;;;;;;;OAOG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAsCpF;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlE;;;;;;;OAOG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAc1C;;;;;;;OAOG;IACG,MAAM,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IASlF;;;;;;OAMG;IACG,MAAM,CAAC,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBtE,YAAY,IAAI,IAAI;IAKpB,WAAW,IAAI,eAAe,EAAE;IAIhC,IAAI,gBAAgB,IAAI,eAAe,EAAE,CAExC;IAED;;;;;;;;OAQG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAY5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* session.ts
|
|
4
|
+
* One browser tab — the main object you interact with.
|
|
5
|
+
*
|
|
6
|
+
* session.goto() — navigate
|
|
7
|
+
* session.click() — AI finds and clicks element
|
|
8
|
+
* session.fill() — AI finds input and types
|
|
9
|
+
* session.type() — type at current focus
|
|
10
|
+
* session.read() — AI reads text from page
|
|
11
|
+
* session.screenshot() — returns base64 JPEG
|
|
12
|
+
* session.scroll() — scroll up/down
|
|
13
|
+
* session.moveTo() — move cursor to element or coords
|
|
14
|
+
* session.capture() — run block while capturing requests
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.Session = void 0;
|
|
18
|
+
const ai_1 = require("./ai");
|
|
19
|
+
const interceptor_1 = require("./interceptor");
|
|
20
|
+
class Session {
|
|
21
|
+
constructor(page, apiKey, model, defaultTimeout) {
|
|
22
|
+
this.page = page;
|
|
23
|
+
this.defaultTimeout = defaultTimeout;
|
|
24
|
+
this.currentUrl = '';
|
|
25
|
+
this.ai = new ai_1.AIClient(apiKey, model);
|
|
26
|
+
this.interceptor = new interceptor_1.Interceptor(page);
|
|
27
|
+
}
|
|
28
|
+
// ─── Navigation ─────────────────────────────────────────────────────────────
|
|
29
|
+
async goto(url, options = {}) {
|
|
30
|
+
const { waitUntil = 'domcontentloaded', timeout = this.defaultTimeout } = options;
|
|
31
|
+
this.currentUrl = url;
|
|
32
|
+
this.interceptor.setSiteUrl(url);
|
|
33
|
+
await this.page.goto(url, { waitUntil, timeout }).catch(async () => {
|
|
34
|
+
await this.page.waitForTimeout(2500);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async wait(ms) {
|
|
38
|
+
await this.page.waitForTimeout(ms);
|
|
39
|
+
}
|
|
40
|
+
// ─── Screenshot ─────────────────────────────────────────────────────────────
|
|
41
|
+
async screenshot() {
|
|
42
|
+
const buf = await this.page.screenshot({ type: 'jpeg', quality: 60 });
|
|
43
|
+
return buf.toString('base64');
|
|
44
|
+
}
|
|
45
|
+
// ─── Click ──────────────────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Click an element described in natural language.
|
|
48
|
+
* AI sees the page and finds the right element.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* await session.click('the Accept cookies button')
|
|
52
|
+
* await session.click('the search icon in the top right')
|
|
53
|
+
*/
|
|
54
|
+
async click(goal) {
|
|
55
|
+
// Layer 1 — try Playwright semantic first (free)
|
|
56
|
+
const semanticResult = await this.semanticClick(goal);
|
|
57
|
+
if (semanticResult)
|
|
58
|
+
return true;
|
|
59
|
+
// Layer 2 — vision
|
|
60
|
+
try {
|
|
61
|
+
const img = await this.screenshot();
|
|
62
|
+
const decision = await this.ai.decide(img, goal);
|
|
63
|
+
if (decision.action === 'none' || !decision.selector)
|
|
64
|
+
return false;
|
|
65
|
+
await this.page.locator(decision.selector).first().click({ timeout: 4000 });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async semanticClick(goal) {
|
|
73
|
+
const lower = goal.toLowerCase();
|
|
74
|
+
// Cookie-like goals: try common patterns
|
|
75
|
+
if (/cookie|accept|consent|agree|dismiss/i.test(lower)) {
|
|
76
|
+
for (const sel of [
|
|
77
|
+
'button:has-text("Accept")', 'button:has-text("Aceptar")',
|
|
78
|
+
'button:has-text("I agree")', 'button:has-text("Got it")',
|
|
79
|
+
'#onetrust-accept-btn-handler', '[class*="cookie"] button',
|
|
80
|
+
]) {
|
|
81
|
+
try {
|
|
82
|
+
const el = this.page.locator(sel).first();
|
|
83
|
+
if (await el.isVisible({ timeout: 400 }).catch(() => false)) {
|
|
84
|
+
await el.click({ timeout: 2000 });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { /* try next */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// ─── Fill ───────────────────────────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Find an input and type a value.
|
|
96
|
+
* AI sees the page and locates the right field.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* await session.fill('BUE', 'the origin airport input')
|
|
100
|
+
* await session.fill('argentina', 'the main search field')
|
|
101
|
+
*/
|
|
102
|
+
async fill(value, goal, options = {}) {
|
|
103
|
+
const { pressEnter = true, delay = 60 } = options;
|
|
104
|
+
// Layer 1 — Playwright semantic roles (free)
|
|
105
|
+
for (const role of ['searchbox', 'textbox']) {
|
|
106
|
+
try {
|
|
107
|
+
const loc = this.page.getByRole(role).first();
|
|
108
|
+
if (await loc.isVisible({ timeout: 500 }).catch(() => false)) {
|
|
109
|
+
await loc.fill('');
|
|
110
|
+
await loc.type(value, { delay });
|
|
111
|
+
if (pressEnter)
|
|
112
|
+
await this.page.keyboard.press('Enter');
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch { /* try next */ }
|
|
117
|
+
}
|
|
118
|
+
// Layer 2 — vision
|
|
119
|
+
try {
|
|
120
|
+
const img = await this.screenshot();
|
|
121
|
+
const decision = await this.ai.decide(img, goal, value);
|
|
122
|
+
if (decision.action === 'none' || !decision.selector)
|
|
123
|
+
return false;
|
|
124
|
+
const el = this.page.locator(decision.selector).first();
|
|
125
|
+
if (decision.action === 'fill') {
|
|
126
|
+
await el.fill('');
|
|
127
|
+
await el.type(value, { delay });
|
|
128
|
+
if (pressEnter)
|
|
129
|
+
await this.page.keyboard.press('Enter');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
await el.click({ timeout: 4000 });
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ─── Type ───────────────────────────────────────────────────────────────────
|
|
141
|
+
/**
|
|
142
|
+
* Type text at the current focused element.
|
|
143
|
+
*/
|
|
144
|
+
async type(text, options = {}) {
|
|
145
|
+
const { pressEnter = false, delay = 60 } = options;
|
|
146
|
+
await this.page.keyboard.type(text, { delay });
|
|
147
|
+
if (pressEnter)
|
|
148
|
+
await this.page.keyboard.press('Enter');
|
|
149
|
+
}
|
|
150
|
+
// ─── Read ───────────────────────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Read text from the page using AI vision.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const title = await session.read('the main article headline')
|
|
156
|
+
* const price = await session.read('the product price')
|
|
157
|
+
* const all = await session.read() // all visible content
|
|
158
|
+
*/
|
|
159
|
+
async read(goal) {
|
|
160
|
+
// Try to get text from DOM first (free)
|
|
161
|
+
if (!goal) {
|
|
162
|
+
const text = await this.page.evaluate(() => document.body.innerText).catch(() => '');
|
|
163
|
+
if (text.length > 50)
|
|
164
|
+
return text.slice(0, 5000);
|
|
165
|
+
}
|
|
166
|
+
// Vision for specific extractions
|
|
167
|
+
const img = await this.screenshot();
|
|
168
|
+
return this.ai.read(img, goal);
|
|
169
|
+
}
|
|
170
|
+
// ─── Scroll ─────────────────────────────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Scroll the page.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* await session.scroll('down')
|
|
176
|
+
* await session.scroll('up', { pixels: 200 })
|
|
177
|
+
* await session.scroll('down', { pixels: 800 })
|
|
178
|
+
*/
|
|
179
|
+
async scroll(direction, options = {}) {
|
|
180
|
+
const { pixels = 400 } = options;
|
|
181
|
+
const delta = direction === 'down' ? pixels : -pixels;
|
|
182
|
+
await this.page.evaluate((d) => window.scrollBy(0, d), delta);
|
|
183
|
+
await this.page.waitForTimeout(300);
|
|
184
|
+
}
|
|
185
|
+
// ─── Move cursor ─────────────────────────────────────────────────────────────
|
|
186
|
+
/**
|
|
187
|
+
* Move cursor to coordinates or to an element described in natural language.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* await session.moveTo({ x: 100, y: 200 })
|
|
191
|
+
* await session.moveTo('the hamburger menu icon')
|
|
192
|
+
*/
|
|
193
|
+
async moveTo(target) {
|
|
194
|
+
if (typeof target === 'object') {
|
|
195
|
+
await this.page.mouse.move(target.x, target.y);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// AI finds the element
|
|
199
|
+
try {
|
|
200
|
+
const img = await this.screenshot();
|
|
201
|
+
const decision = await this.ai.decide(img, `hover over: ${target}`);
|
|
202
|
+
if (decision.action === 'none' || !decision.selector)
|
|
203
|
+
return;
|
|
204
|
+
await this.page.locator(decision.selector).first().hover({ timeout: 3000 });
|
|
205
|
+
}
|
|
206
|
+
catch { /* ignore */ }
|
|
207
|
+
}
|
|
208
|
+
// ─── Request capture ─────────────────────────────────────────────────────────
|
|
209
|
+
startCapture() {
|
|
210
|
+
this.interceptor.setSiteUrl(this.currentUrl);
|
|
211
|
+
this.interceptor.start();
|
|
212
|
+
}
|
|
213
|
+
stopCapture() {
|
|
214
|
+
return this.interceptor.stop();
|
|
215
|
+
}
|
|
216
|
+
get capturedRequests() {
|
|
217
|
+
return this.interceptor.ranked();
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Run a block and return captured + scored requests after.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* const requests = await session.capture(async () => {
|
|
224
|
+
* await session.fill('BUE', 'origin airport')
|
|
225
|
+
* await session.click('search button')
|
|
226
|
+
* })
|
|
227
|
+
*/
|
|
228
|
+
async capture(fn) {
|
|
229
|
+
this.startCapture();
|
|
230
|
+
try {
|
|
231
|
+
await fn();
|
|
232
|
+
await this.page.waitForTimeout(2000);
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
return this.stopCapture();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// ─── Lifecycle ───────────────────────────────────────────────────────────────
|
|
239
|
+
async close() {
|
|
240
|
+
await this.page.close().catch(() => { });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
exports.Session = Session;
|
|
244
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface RensanConfig {
|
|
2
|
+
/** Anthropic API key */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Vision model for page understanding. Default: claude-haiku-4-5 */
|
|
5
|
+
model?: string;
|
|
6
|
+
/** Run browser headless. Default: true */
|
|
7
|
+
headless?: boolean;
|
|
8
|
+
/** Default navigation timeout ms. Default: 25000 */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GotoOptions {
|
|
12
|
+
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface FillOptions {
|
|
16
|
+
/** Press Enter after typing. Default: true */
|
|
17
|
+
pressEnter?: boolean;
|
|
18
|
+
/** Delay between keystrokes ms. Default: 60 */
|
|
19
|
+
delay?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ScrollOptions {
|
|
22
|
+
/** Pixels to scroll. Default: 400 */
|
|
23
|
+
pixels?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface CapturedRequest {
|
|
26
|
+
url: string;
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
score: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AIDecision {
|
|
31
|
+
action: 'click' | 'fill' | 'none';
|
|
32
|
+
selector: string;
|
|
33
|
+
confidence: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ReadResult {
|
|
36
|
+
text: string;
|
|
37
|
+
raw: string;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAID,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,iFAAiF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rensan-browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered browser for agents — navigate, click, type, read, see images, capture requests",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": ["browser", "ai", "agent", "playwright", "automation", "vision", "scraping", "claude"],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"playwright": ">=1.46.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@anthropic-ai/sdk": "^0.39.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"playwright": "^1.58.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|