@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.
Files changed (68) hide show
  1. package/README.md +104 -0
  2. package/dist/agentpay-gateway.d.ts +8 -0
  3. package/dist/agentpay-gateway.d.ts.map +1 -0
  4. package/dist/agentpay-gateway.js +58 -0
  5. package/dist/commands/act.d.ts +5 -0
  6. package/dist/commands/act.d.ts.map +1 -0
  7. package/dist/commands/act.js +30 -0
  8. package/dist/commands/captcha-solve.d.ts +6 -0
  9. package/dist/commands/captcha-solve.d.ts.map +1 -0
  10. package/dist/commands/captcha-solve.js +36 -0
  11. package/dist/commands/close.d.ts +5 -0
  12. package/dist/commands/close.d.ts.map +1 -0
  13. package/dist/commands/close.js +32 -0
  14. package/dist/commands/extract.d.ts +5 -0
  15. package/dist/commands/extract.d.ts.map +1 -0
  16. package/dist/commands/extract.js +59 -0
  17. package/dist/commands/launch.d.ts +10 -0
  18. package/dist/commands/launch.d.ts.map +1 -0
  19. package/dist/commands/launch.js +132 -0
  20. package/dist/commands/navigate.d.ts +5 -0
  21. package/dist/commands/navigate.d.ts.map +1 -0
  22. package/dist/commands/navigate.js +26 -0
  23. package/dist/commands/observe.d.ts +5 -0
  24. package/dist/commands/observe.d.ts.map +1 -0
  25. package/dist/commands/observe.js +36 -0
  26. package/dist/commands/screenshot.d.ts +5 -0
  27. package/dist/commands/screenshot.d.ts.map +1 -0
  28. package/dist/commands/screenshot.js +27 -0
  29. package/dist/commands/status.d.ts +5 -0
  30. package/dist/commands/status.d.ts.map +1 -0
  31. package/dist/commands/status.js +47 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +247 -0
  35. package/dist/output.d.ts +20 -0
  36. package/dist/output.d.ts.map +1 -0
  37. package/dist/output.js +48 -0
  38. package/dist/session.d.ts +22 -0
  39. package/dist/session.d.ts.map +1 -0
  40. package/dist/session.js +63 -0
  41. package/dist/solver/browser-launcher.d.ts +12 -0
  42. package/dist/solver/browser-launcher.d.ts.map +1 -0
  43. package/dist/solver/browser-launcher.js +265 -0
  44. package/dist/solver/captcha-detector.d.ts +22 -0
  45. package/dist/solver/captcha-detector.d.ts.map +1 -0
  46. package/dist/solver/captcha-detector.js +463 -0
  47. package/dist/solver/captcha-runtime.d.ts +18 -0
  48. package/dist/solver/captcha-runtime.d.ts.map +1 -0
  49. package/dist/solver/captcha-runtime.js +152 -0
  50. package/dist/solver/captcha-solver.d.ts +24 -0
  51. package/dist/solver/captcha-solver.d.ts.map +1 -0
  52. package/dist/solver/captcha-solver.js +88 -0
  53. package/dist/solver/config.d.ts +12 -0
  54. package/dist/solver/config.d.ts.map +1 -0
  55. package/dist/solver/config.js +67 -0
  56. package/dist/solver/fingerprint.d.ts +9 -0
  57. package/dist/solver/fingerprint.d.ts.map +1 -0
  58. package/dist/solver/fingerprint.js +96 -0
  59. package/dist/solver/profile-manager.d.ts +8 -0
  60. package/dist/solver/profile-manager.d.ts.map +1 -0
  61. package/dist/solver/profile-manager.js +74 -0
  62. package/dist/solver/types.d.ts +66 -0
  63. package/dist/solver/types.d.ts.map +1 -0
  64. package/dist/solver/types.js +1 -0
  65. package/dist/stagehand.d.ts +20 -0
  66. package/dist/stagehand.d.ts.map +1 -0
  67. package/dist/stagehand.js +46 -0
  68. 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,5 @@
1
+ /**
2
+ * browse act "<instruction>" [--variables '<json>'] — Perform an action on the page.
3
+ */
4
+ export declare function act(cdpUrl: string, instruction: string, variables?: Record<string, string>): Promise<void>;
5
+ //# sourceMappingURL=act.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse close — Close the browser and clean up session.
3
+ */
4
+ export declare function close(): Promise<void>;
5
+ //# sourceMappingURL=close.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse extract "<instruction>" [--schema '<json>'] — Extract structured data.
3
+ */
4
+ export declare function extract(cdpUrl: string, instruction: string, schemaJson?: string): Promise<void>;
5
+ //# sourceMappingURL=extract.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse navigate <url> — Navigate the current tab to a URL.
3
+ */
4
+ export declare function navigate(cdpUrl: string, targetUrl: string): Promise<void>;
5
+ //# sourceMappingURL=navigate.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse observe ["<instruction>"] — Discover available actions on the page.
3
+ */
4
+ export declare function observe(cdpUrl: string, instruction?: string): Promise<void>;
5
+ //# sourceMappingURL=observe.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse screenshot [--path <file>] — Capture a screenshot of the current page.
3
+ */
4
+ export declare function screenshot(cdpUrl: string, filePath?: string): Promise<void>;
5
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * browse status — Check browser state (alive, current URL, title).
3
+ */
4
+ export declare function status(): Promise<void>;
5
+ //# sourceMappingURL=status.d.ts.map
@@ -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"}