@nuanu-ai/agentbrowse 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 +104 -0
- package/dist/agentpay-gateway.d.ts +8 -0
- package/dist/agentpay-gateway.d.ts.map +1 -0
- package/dist/agentpay-gateway.js +58 -0
- package/dist/commands/act.d.ts +5 -0
- package/dist/commands/act.d.ts.map +1 -0
- package/dist/commands/act.js +30 -0
- package/dist/commands/captcha-solve.d.ts +6 -0
- package/dist/commands/captcha-solve.d.ts.map +1 -0
- package/dist/commands/captcha-solve.js +36 -0
- package/dist/commands/close.d.ts +5 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +32 -0
- package/dist/commands/extract.d.ts +5 -0
- package/dist/commands/extract.d.ts.map +1 -0
- package/dist/commands/extract.js +59 -0
- package/dist/commands/launch.d.ts +10 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +132 -0
- package/dist/commands/navigate.d.ts +5 -0
- package/dist/commands/navigate.d.ts.map +1 -0
- package/dist/commands/navigate.js +26 -0
- package/dist/commands/observe.d.ts +5 -0
- package/dist/commands/observe.d.ts.map +1 -0
- package/dist/commands/observe.js +36 -0
- package/dist/commands/screenshot.d.ts +5 -0
- package/dist/commands/screenshot.d.ts.map +1 -0
- package/dist/commands/screenshot.js +27 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +47 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +247 -0
- package/dist/output.d.ts +20 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +48 -0
- package/dist/session.d.ts +22 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +63 -0
- package/dist/solver/browser-launcher.d.ts +12 -0
- package/dist/solver/browser-launcher.d.ts.map +1 -0
- package/dist/solver/browser-launcher.js +265 -0
- package/dist/solver/captcha-detector.d.ts +22 -0
- package/dist/solver/captcha-detector.d.ts.map +1 -0
- package/dist/solver/captcha-detector.js +463 -0
- package/dist/solver/captcha-runtime.d.ts +18 -0
- package/dist/solver/captcha-runtime.d.ts.map +1 -0
- package/dist/solver/captcha-runtime.js +152 -0
- package/dist/solver/captcha-solver.d.ts +24 -0
- package/dist/solver/captcha-solver.d.ts.map +1 -0
- package/dist/solver/captcha-solver.js +88 -0
- package/dist/solver/config.d.ts +12 -0
- package/dist/solver/config.d.ts.map +1 -0
- package/dist/solver/config.js +67 -0
- package/dist/solver/fingerprint.d.ts +9 -0
- package/dist/solver/fingerprint.d.ts.map +1 -0
- package/dist/solver/fingerprint.js +96 -0
- package/dist/solver/profile-manager.d.ts +8 -0
- package/dist/solver/profile-manager.d.ts.map +1 -0
- package/dist/solver/profile-manager.js +74 -0
- package/dist/solver/types.d.ts +66 -0
- package/dist/solver/types.d.ts.map +1 -0
- package/dist/solver/types.js +1 -0
- package/dist/stagehand.d.ts +20 -0
- package/dist/stagehand.d.ts.map +1 -0
- package/dist/stagehand.js +46 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @nuanu-ai/agentbrowse
|
|
2
|
+
|
|
3
|
+
Standalone browser automation CLI for AI agents.
|
|
4
|
+
|
|
5
|
+
`agentbrowse` launches and controls a CDP-reachable browser, navigates pages,
|
|
6
|
+
observes UI state, performs actions, extracts data, captures screenshots, and
|
|
7
|
+
optionally solves captchas when the active session supports it.
|
|
8
|
+
|
|
9
|
+
This package is not a payment tool. Payment execution belongs in `agentpay`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Run without installing globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @nuanu-ai/agentbrowse launch https://example.com
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i -g @nuanu-ai/agentbrowse
|
|
23
|
+
agentbrowse launch https://example.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js 18+
|
|
29
|
+
- a Chrome-compatible browser runtime available on the machine
|
|
30
|
+
- an Anthropic API key for browsing commands
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
Launch browser and optionally navigate:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
agentbrowse launch https://example.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Navigate current session:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
agentbrowse navigate https://example.com/checkout
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Act on the page:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agentbrowse act "click the Buy button"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Extract data:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
agentbrowse extract "get product name and price"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Observe available actions/elements:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
agentbrowse observe
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Solve captcha when the active session supports it:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
agentbrowse solve-captcha --timeout 90
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Inspect or close the current session:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
agentbrowse status
|
|
74
|
+
agentbrowse close
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configure
|
|
78
|
+
|
|
79
|
+
Browsing commands use Anthropic via Stagehand:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export ANTHROPIC_API_KEY=...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Optional model override:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export AGENTBROWSE_MODEL=anthropic/claude-haiku-4-5-20251001
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`solve-captcha` is separate and uses AgentPay gateway credentials only when
|
|
92
|
+
that command is invoked.
|
|
93
|
+
|
|
94
|
+
## Runtime model
|
|
95
|
+
|
|
96
|
+
- `agentbrowse` persists the active browser session under `~/.agentpay`
|
|
97
|
+
- browsing commands require `ANTHROPIC_API_KEY`
|
|
98
|
+
- normal browsing commands do not require `AGENTPAY_API_KEY`
|
|
99
|
+
- `solve-captcha` requires both:
|
|
100
|
+
- a session with captcha-solving capability
|
|
101
|
+
- AgentPay gateway configuration
|
|
102
|
+
|
|
103
|
+
Human-readable progress is written to `stderr`. Command results are written to
|
|
104
|
+
`stdout`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AgentpayGatewayConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function tryResolveAgentpayGatewayConfig(): AgentpayGatewayConfig | null;
|
|
6
|
+
export declare function resolveAgentpayGatewayConfig(): AgentpayGatewayConfig;
|
|
7
|
+
export declare function applyAgentpayGatewayEnv(gateway: AgentpayGatewayConfig): void;
|
|
8
|
+
//# sourceMappingURL=agentpay-gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentpay-gateway.d.ts","sourceRoot":"","sources":["../src/agentpay-gateway.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAiCD,wBAAgB,+BAA+B,IAAI,qBAAqB,GAAG,IAAI,CAoB9E;AAED,wBAAgB,4BAA4B,IAAI,qBAAqB,CAMpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAG5E"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readConfig } from './solver/config.js';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const DEFAULT_AGENTPAY_API_URL = 'https://durcottggsiesxxqzvbb.supabase.co/functions/v1/api';
|
|
6
|
+
function trimOrUndefined(value) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return undefined;
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
11
|
+
}
|
|
12
|
+
function normalizeApiUrl(value) {
|
|
13
|
+
return value.replace(/\/$/, '');
|
|
14
|
+
}
|
|
15
|
+
function readAgentpayCliConfig() {
|
|
16
|
+
const primaryPath = join(homedir(), '.agentpay', 'config.json');
|
|
17
|
+
const legacyPath = join(homedir(), '.agentpay', 'agentpay-cli', 'config.json');
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(primaryPath, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(legacyPath, 'utf-8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function tryResolveAgentpayGatewayConfig() {
|
|
31
|
+
const solverConfig = readConfig();
|
|
32
|
+
const cliConfig = readAgentpayCliConfig();
|
|
33
|
+
const apiKey = trimOrUndefined(process.env.AGENTPAY_API_KEY) ||
|
|
34
|
+
trimOrUndefined(solverConfig.agentpay?.apiKey) ||
|
|
35
|
+
trimOrUndefined(cliConfig.agentpay?.apiKey);
|
|
36
|
+
const apiUrl = trimOrUndefined(process.env.AGENTPAY_API_URL) ||
|
|
37
|
+
trimOrUndefined(solverConfig.agentpay?.apiUrl) ||
|
|
38
|
+
trimOrUndefined(cliConfig.agentpay?.apiUrl) ||
|
|
39
|
+
DEFAULT_AGENTPAY_API_URL;
|
|
40
|
+
if (!apiKey) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
apiKey,
|
|
45
|
+
apiUrl: normalizeApiUrl(apiUrl),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function resolveAgentpayGatewayConfig() {
|
|
49
|
+
const gateway = tryResolveAgentpayGatewayConfig();
|
|
50
|
+
if (!gateway) {
|
|
51
|
+
throw new Error('Captcha solving requires API key configuration.');
|
|
52
|
+
}
|
|
53
|
+
return gateway;
|
|
54
|
+
}
|
|
55
|
+
export function applyAgentpayGatewayEnv(gateway) {
|
|
56
|
+
process.env.AGENTPAY_API_KEY = gateway.apiKey;
|
|
57
|
+
process.env.AGENTPAY_API_URL = gateway.apiUrl;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/commands/act.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAsB,GAAG,CACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse act "<instruction>" [--variables '<json>'] — Perform an action on the page.
|
|
3
|
+
*/
|
|
4
|
+
import { connectStagehand, getPageInfo } from '../stagehand.js';
|
|
5
|
+
import { outputJSON, outputError } from '../output.js';
|
|
6
|
+
export async function act(cdpUrl, instruction, variables) {
|
|
7
|
+
let stagehand;
|
|
8
|
+
try {
|
|
9
|
+
stagehand = await connectStagehand(cdpUrl);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
outputError(`Failed to connect to browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const result = await stagehand.act(instruction, variables ? { variables } : undefined);
|
|
16
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
17
|
+
await stagehand.close();
|
|
18
|
+
outputJSON({
|
|
19
|
+
success: result.success,
|
|
20
|
+
action: result.actionDescription ?? instruction,
|
|
21
|
+
message: result.message,
|
|
22
|
+
url,
|
|
23
|
+
title,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
await stagehand.close();
|
|
28
|
+
outputError(`Act failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse solve-captcha [--timeout <seconds>] — Solve captcha on current page.
|
|
3
|
+
*/
|
|
4
|
+
import type { BrowseSession } from '../session.js';
|
|
5
|
+
export declare function captchaSolve(session: BrowseSession | null, timeoutSeconds?: number): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=captcha-solve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captcha-solve.d.ts","sourceRoot":"","sources":["../../src/commands/captcha-solve.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAOnD,wBAAsB,YAAY,CAChC,OAAO,EAAE,aAAa,GAAG,IAAI,EAC7B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse solve-captcha [--timeout <seconds>] — Solve captcha on current page.
|
|
3
|
+
*/
|
|
4
|
+
import { solveCaptchasByCdp } from '../solver/captcha-runtime.js';
|
|
5
|
+
import { supportsCaptchaSolve } from '../session.js';
|
|
6
|
+
import { info, outputError, outputJSON } from '../output.js';
|
|
7
|
+
import { applyAgentpayGatewayEnv, resolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
|
|
8
|
+
const DEFAULT_TIMEOUT_SECONDS = 90;
|
|
9
|
+
export async function captchaSolve(session, timeoutSeconds) {
|
|
10
|
+
if (!session) {
|
|
11
|
+
outputError('Captcha solving requires an active browser session started with browse launch.');
|
|
12
|
+
}
|
|
13
|
+
if (!supportsCaptchaSolve(session)) {
|
|
14
|
+
outputError('Current browser session does not support captcha solving.');
|
|
15
|
+
}
|
|
16
|
+
const gateway = resolveAgentpayGatewayConfig();
|
|
17
|
+
applyAgentpayGatewayEnv(gateway);
|
|
18
|
+
const cdpUrl = session.cdpUrl;
|
|
19
|
+
const timeoutMs = Math.max(1, timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000;
|
|
20
|
+
info(`[captcha] solve-captcha started (timeout ${Math.ceil(timeoutMs / 1000)}s, endpoint ${cdpUrl})`);
|
|
21
|
+
const result = await solveCaptchasByCdp(cdpUrl, gateway.apiKey, {
|
|
22
|
+
timeoutMs,
|
|
23
|
+
solveTimeoutMs: Math.min(timeoutMs, 120000),
|
|
24
|
+
apiUrl: gateway.apiUrl,
|
|
25
|
+
onProgress: (message) => info(message),
|
|
26
|
+
});
|
|
27
|
+
outputJSON({
|
|
28
|
+
success: true,
|
|
29
|
+
solved: result.solved,
|
|
30
|
+
verified: result.verified,
|
|
31
|
+
unresolved: result.unresolved,
|
|
32
|
+
unresolvedCaptchas: result.unresolvedCaptchas,
|
|
33
|
+
detected: result.detected,
|
|
34
|
+
timedOut: result.timedOut,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA0B3C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse close — Close the browser and clean up session.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { loadSession, deleteSession, getSessionPort } from '../session.js';
|
|
6
|
+
import { outputJSON, info } from '../output.js';
|
|
7
|
+
export async function close() {
|
|
8
|
+
const session = loadSession();
|
|
9
|
+
const port = getSessionPort(session);
|
|
10
|
+
// Kill Chrome on the current session CDP port regardless of session state
|
|
11
|
+
try {
|
|
12
|
+
const out = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
|
|
13
|
+
if (out) {
|
|
14
|
+
for (const pid of out.split('\n')) {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(Number(pid), 'SIGTERM');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* already dead */
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
info(`Killed Chrome on port ${port}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* no process on port */
|
|
27
|
+
}
|
|
28
|
+
if (session) {
|
|
29
|
+
deleteSession();
|
|
30
|
+
}
|
|
31
|
+
outputJSON({ success: true });
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/commands/extract.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2BH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse extract "<instruction>" [--schema '<json>'] — Extract structured data.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { connectStagehand, getPageInfo } from '../stagehand.js';
|
|
6
|
+
import { outputJSON, outputError } from '../output.js';
|
|
7
|
+
/**
|
|
8
|
+
* Build a Zod schema from a simple JSON type descriptor.
|
|
9
|
+
* Supports: "string", "number", "boolean", and nested objects.
|
|
10
|
+
*
|
|
11
|
+
* Example: {"price": "number", "name": "string"} → z.object({price: z.number(), name: z.string()})
|
|
12
|
+
*/
|
|
13
|
+
function buildSchema(descriptor) {
|
|
14
|
+
const shape = {};
|
|
15
|
+
for (const [key, value] of Object.entries(descriptor)) {
|
|
16
|
+
if (value === 'string')
|
|
17
|
+
shape[key] = z.string();
|
|
18
|
+
else if (value === 'number')
|
|
19
|
+
shape[key] = z.number();
|
|
20
|
+
else if (value === 'boolean')
|
|
21
|
+
shape[key] = z.boolean();
|
|
22
|
+
else if (typeof value === 'object' && value !== null) {
|
|
23
|
+
shape[key] = buildSchema(value);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
shape[key] = z.unknown();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return z.object(shape);
|
|
30
|
+
}
|
|
31
|
+
export async function extract(cdpUrl, instruction, schemaJson) {
|
|
32
|
+
let stagehand;
|
|
33
|
+
try {
|
|
34
|
+
stagehand = await connectStagehand(cdpUrl);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
outputError(`Failed to connect to browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
if (schemaJson) {
|
|
41
|
+
const descriptor = JSON.parse(schemaJson);
|
|
42
|
+
const schema = buildSchema(descriptor);
|
|
43
|
+
const data = await stagehand.extract(instruction, schema);
|
|
44
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
45
|
+
await stagehand.close();
|
|
46
|
+
outputJSON({ success: true, data, url, title });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const result = await stagehand.extract(instruction);
|
|
50
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
51
|
+
await stagehand.close();
|
|
52
|
+
outputJSON({ success: true, extraction: result.extraction, url, title });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
await stagehand.close();
|
|
57
|
+
outputError(`Extract failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse launch [url] — Start browser session, optionally navigate.
|
|
3
|
+
*/
|
|
4
|
+
export type LaunchCliOptions = {
|
|
5
|
+
compact?: boolean;
|
|
6
|
+
profile?: string;
|
|
7
|
+
headless?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,wBAAsB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse launch [url] — Start browser session, optionally navigate.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { readConfig } from '../solver/config.js';
|
|
6
|
+
import { ensureProfile } from '../solver/profile-manager.js';
|
|
7
|
+
import { launchSolver } from '../solver/browser-launcher.js';
|
|
8
|
+
import { saveSession } from '../session.js';
|
|
9
|
+
import { findChromePid } from '../stagehand.js';
|
|
10
|
+
import { outputJSON, outputError } from '../output.js';
|
|
11
|
+
import { applyAgentpayGatewayEnv, tryResolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
|
|
12
|
+
const CDP_PORT = 9222;
|
|
13
|
+
const DEFAULT_PROFILE = 'default';
|
|
14
|
+
const COMPACT_WINDOW = {
|
|
15
|
+
width: 1280,
|
|
16
|
+
height: 900,
|
|
17
|
+
};
|
|
18
|
+
export async function launch(url, opts) {
|
|
19
|
+
await killChromeOnPort(CDP_PORT);
|
|
20
|
+
const compact = opts?.compact ?? true;
|
|
21
|
+
const profileName = opts?.profile ?? DEFAULT_PROFILE;
|
|
22
|
+
const headless = opts?.headless ?? false;
|
|
23
|
+
await launchManaged(url, profileName, headless, compact);
|
|
24
|
+
}
|
|
25
|
+
async function killChromeOnPort(port) {
|
|
26
|
+
const pids = getPidsOnPort(port);
|
|
27
|
+
if (pids.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
for (const pid of pids) {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 'SIGTERM');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Skip stale PID.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (let i = 0; i < 20; i++) {
|
|
38
|
+
if (getPidsOnPort(port).length === 0)
|
|
39
|
+
return;
|
|
40
|
+
await sleep(100);
|
|
41
|
+
}
|
|
42
|
+
for (const pid of getPidsOnPort(port)) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 'SIGKILL');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Skip stale PID.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function launchManaged(url, profileName, headless, compact) {
|
|
52
|
+
let session;
|
|
53
|
+
try {
|
|
54
|
+
const profile = ensureProfile(profileName);
|
|
55
|
+
const config = readConfig();
|
|
56
|
+
const gateway = tryResolveAgentpayGatewayConfig();
|
|
57
|
+
if (gateway) {
|
|
58
|
+
applyAgentpayGatewayEnv(gateway);
|
|
59
|
+
}
|
|
60
|
+
session = await launchSolver(profile, {
|
|
61
|
+
headless: headless || config.defaults?.headless,
|
|
62
|
+
url,
|
|
63
|
+
cdpPort: CDP_PORT,
|
|
64
|
+
windowSize: compact ? COMPACT_WINDOW : undefined,
|
|
65
|
+
captchaSolver: gateway
|
|
66
|
+
? {
|
|
67
|
+
apiKey: gateway.apiKey,
|
|
68
|
+
apiUrl: gateway.apiUrl,
|
|
69
|
+
autoSolve: config.defaults?.captchaAutoSolve !== false,
|
|
70
|
+
}
|
|
71
|
+
: undefined,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
outputError(`Failed to launch browser: ${formatUnknownError(err)}`);
|
|
76
|
+
}
|
|
77
|
+
const chromePid = findChromePid(CDP_PORT) ?? process.pid;
|
|
78
|
+
saveSession({
|
|
79
|
+
cdpUrl: session.cdpUrl,
|
|
80
|
+
pid: chromePid,
|
|
81
|
+
port: CDP_PORT,
|
|
82
|
+
profile: profileName,
|
|
83
|
+
launchedAt: new Date().toISOString(),
|
|
84
|
+
capabilities: {
|
|
85
|
+
captchaSolve: true,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const currentUrl = session.page.url();
|
|
89
|
+
const title = await session.page.title();
|
|
90
|
+
await session.disconnect();
|
|
91
|
+
outputJSON({
|
|
92
|
+
success: true,
|
|
93
|
+
runtime: 'managed',
|
|
94
|
+
captchaSolveCapable: true,
|
|
95
|
+
profile: profileName,
|
|
96
|
+
cdpUrl: session.cdpUrl,
|
|
97
|
+
url: currentUrl,
|
|
98
|
+
title,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function getPidsOnPort(port) {
|
|
102
|
+
try {
|
|
103
|
+
const out = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
|
|
104
|
+
if (!out)
|
|
105
|
+
return [];
|
|
106
|
+
return out
|
|
107
|
+
.split('\n')
|
|
108
|
+
.map((value) => Number(value))
|
|
109
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function formatUnknownError(err) {
|
|
116
|
+
if (err instanceof Error)
|
|
117
|
+
return err.message;
|
|
118
|
+
if (typeof err === 'string')
|
|
119
|
+
return err;
|
|
120
|
+
if (err && typeof err === 'object') {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.stringify(err);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return Object.prototype.toString.call(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return String(err);
|
|
129
|
+
}
|
|
130
|
+
function sleep(ms) {
|
|
131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigate.d.ts","sourceRoot":"","sources":["../../src/commands/navigate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB/E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse navigate <url> — Navigate the current tab to a URL.
|
|
3
|
+
*/
|
|
4
|
+
import { connectStagehand, getPageInfo } from '../stagehand.js';
|
|
5
|
+
import { outputJSON, outputError, info } from '../output.js';
|
|
6
|
+
export async function navigate(cdpUrl, targetUrl) {
|
|
7
|
+
let stagehand;
|
|
8
|
+
try {
|
|
9
|
+
stagehand = await connectStagehand(cdpUrl);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
outputError(`Failed to connect to browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const page = stagehand.context.pages()[0];
|
|
16
|
+
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
|
17
|
+
info(`Navigated to: ${targetUrl}`);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
await stagehand.close();
|
|
21
|
+
outputError(`Navigation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
22
|
+
}
|
|
23
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
24
|
+
await stagehand.close();
|
|
25
|
+
outputJSON({ success: true, url, title });
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../src/commands/observe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCjF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse observe ["<instruction>"] — Discover available actions on the page.
|
|
3
|
+
*/
|
|
4
|
+
import { connectStagehand, getPageInfo } from '../stagehand.js';
|
|
5
|
+
import { outputJSON, outputError } from '../output.js';
|
|
6
|
+
export async function observe(cdpUrl, instruction) {
|
|
7
|
+
let stagehand;
|
|
8
|
+
try {
|
|
9
|
+
stagehand = await connectStagehand(cdpUrl);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
outputError(`Failed to connect to browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const actions = instruction
|
|
16
|
+
? await stagehand.observe(instruction)
|
|
17
|
+
: await stagehand.observe();
|
|
18
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
19
|
+
await stagehand.close();
|
|
20
|
+
outputJSON({
|
|
21
|
+
success: true,
|
|
22
|
+
actions: actions.map((a) => ({
|
|
23
|
+
description: a.description,
|
|
24
|
+
selector: a.selector,
|
|
25
|
+
method: a.method,
|
|
26
|
+
arguments: a.arguments,
|
|
27
|
+
})),
|
|
28
|
+
url,
|
|
29
|
+
title,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
await stagehand.close();
|
|
34
|
+
outputError(`Observe failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/commands/screenshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBjF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse screenshot [--path <file>] — Capture a screenshot of the current page.
|
|
3
|
+
*/
|
|
4
|
+
import { connectStagehand, getPageInfo } from '../stagehand.js';
|
|
5
|
+
import { outputJSON, outputError, info } from '../output.js';
|
|
6
|
+
export async function screenshot(cdpUrl, filePath) {
|
|
7
|
+
const outputPath = filePath ?? `/tmp/browse-screenshot-${Date.now()}.png`;
|
|
8
|
+
let stagehand;
|
|
9
|
+
try {
|
|
10
|
+
stagehand = await connectStagehand(cdpUrl);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
outputError(`Failed to connect to browser: ${err instanceof Error ? err.message : String(err)}`);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const page = stagehand.context.pages()[0];
|
|
17
|
+
await page.screenshot({ path: outputPath });
|
|
18
|
+
info(`Screenshot saved: ${outputPath}`);
|
|
19
|
+
const { url, title } = await getPageInfo(stagehand);
|
|
20
|
+
await stagehand.close();
|
|
21
|
+
outputJSON({ success: true, path: outputPath, url, title });
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
await stagehand.close();
|
|
25
|
+
outputError(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAgD5C"}
|