@nuanu-ai/agentbrowse 0.2.22 → 0.2.24

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 CHANGED
@@ -47,6 +47,16 @@ agentbrowse launch https://example.com
47
47
  agentbrowse launch https://example.com --headless
48
48
  ```
49
49
 
50
+ By default, `launch` runs in headful mode. Use `--headless` only when you
51
+ intentionally want a hidden browser:
52
+
53
+ ```bash
54
+ agentbrowse launch https://example.com --headless
55
+ ```
56
+
57
+ If you want to state the default mode explicitly in scripts, use
58
+ `--headful`.
59
+
50
60
  Navigate current session:
51
61
 
52
62
  ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/commands/act.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAqCnD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAyHxF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,CAAC;AA4V7B,wBAAsB,GAAG,CACvB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAmsBf"}
1
+ {"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../src/commands/act.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAsCnD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA2HxF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,CAAC;AA4V7B,wBAAsB,GAAG,CACvB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAitBf"}
@@ -7,6 +7,7 @@ import { outputContractFailure, outputFailure, outputJSON } from '../output.js';
7
7
  import { capturePageObservation, captureLocatorContextHash, captureLocatorState, createAcceptanceProbe, diagnoseNoObservableProgress, genericClickObservationChanged, locatorStateChanged, pageObservationChanged, rankLocatorCandidates, shouldVerifyObservableProgress, waitForAcceptanceProbe, } from './action-acceptance.js';
8
8
  import { captureActionFailureArtifacts, startActionTrace } from './action-artifacts.js';
9
9
  import { buildLocator, resolveLocatorRoot } from './action-fallbacks.js';
10
+ import { clickActivationStrategyForTarget } from './click-activation-policy.js';
10
11
  import { resolveSubmitResult } from './action-result-resolution.js';
11
12
  import { projectActionValue } from './action-value-projection.js';
12
13
  import { applyActionWithFallbacks } from './action-executor.js';
@@ -23,6 +24,7 @@ function ensureValue(action, value) {
23
24
  return value;
24
25
  throw new Error(`Act value is required for action: ${action}`);
25
26
  }
27
+ const MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS = [0, 25, 50];
26
28
  function emitActPreflightFailure(params) {
27
29
  return outputContractFailure({
28
30
  error: params.error,
@@ -465,30 +467,41 @@ export async function act(session, targetRef, action, value) {
465
467
  if (!liveTarget.domSignature) {
466
468
  return false;
467
469
  }
468
- const snapshot = await readLocatorBindingSnapshot(resolvedLocator).catch(() => null);
469
- if (!snapshot?.domSignature || snapshot.domSignature === liveTarget.domSignature) {
470
- return false;
471
- }
472
- if (!isCompatibleMutableFieldBinding(liveTarget, snapshot)) {
473
- return false;
474
- }
475
- attempts.push(`domSignature.rebound:${strategy}`);
476
- const updatedTarget = updateTarget(session, targetRef, {
477
- domSignature: snapshot.domSignature,
478
- label: snapshot.label ?? liveTarget.label,
479
- lifecycle: 'live',
480
- lifecycleReason: undefined,
481
- availability: { state: 'available' },
482
- semantics: {
483
- ...liveTarget.semantics,
484
- name: snapshot.label ?? liveTarget.semantics?.name,
485
- role: snapshot.role ?? liveTarget.semantics?.role,
486
- },
487
- });
488
- if (updatedTarget) {
489
- liveTarget = updatedTarget;
470
+ for (let attemptIndex = 0; attemptIndex < MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS.length; attemptIndex += 1) {
471
+ const delayMs = MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS[attemptIndex] ?? 0;
472
+ if (attemptIndex > 0) {
473
+ attempts.push(`domSignature.rebind.retry:${strategy}:${attemptIndex + 1}`);
474
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
475
+ }
476
+ const snapshot = await readLocatorBindingSnapshot(resolvedLocator).catch(() => null);
477
+ if (!snapshot?.domSignature) {
478
+ continue;
479
+ }
480
+ if (snapshot.domSignature === liveTarget.domSignature) {
481
+ return false;
482
+ }
483
+ if (!isCompatibleMutableFieldBinding(liveTarget, snapshot)) {
484
+ continue;
485
+ }
486
+ attempts.push(`domSignature.rebound:${strategy}`);
487
+ const updatedTarget = updateTarget(session, targetRef, {
488
+ domSignature: snapshot.domSignature,
489
+ label: snapshot.label ?? liveTarget.label,
490
+ lifecycle: 'live',
491
+ lifecycleReason: undefined,
492
+ availability: { state: 'available' },
493
+ semantics: {
494
+ ...liveTarget.semantics,
495
+ name: snapshot.label ?? liveTarget.semantics?.name,
496
+ role: snapshot.role ?? liveTarget.semantics?.role,
497
+ },
498
+ });
499
+ if (updatedTarget) {
500
+ liveTarget = updatedTarget;
501
+ }
502
+ return true;
490
503
  }
491
- return true;
504
+ return false;
492
505
  };
493
506
  const assertResolvedTargetStillValid = async (resolvedLocator, stage) => {
494
507
  if (liveTarget.pageSignature &&
@@ -596,6 +609,7 @@ export async function act(session, targetRef, action, value) {
596
609
  });
597
610
  attempts.push(`resolve:${strategy}`);
598
611
  const usedFallback = await applyActionWithFallbacks(page, locatorRoot, resolvedLocator, action, executionValue, attempts, target.controlFamily, {
612
+ clickActivationStrategy: clickActivationStrategyForTarget(target, action),
599
613
  guards: {
600
614
  assertStillValid: async (stage) => {
601
615
  await assertResolvedTargetStillValid(resolvedLocator, stage);
@@ -3,9 +3,11 @@ import type { TargetControlFamily } from '../runtime-state.js';
3
3
  import type { BrowseAction } from './browse-actions.js';
4
4
  import type { LocatorRoot } from './action-fallbacks.js';
5
5
  import type { ActionExecutionGuards } from './action-execution-guards.js';
6
+ import type { ClickActivationStrategy } from './click-activation-policy.js';
6
7
  type ActionExecutionOptions = {
7
8
  beforeClickRetry?: () => Promise<void>;
8
9
  guards?: ActionExecutionGuards;
10
+ clickActivationStrategy?: ClickActivationStrategy;
9
11
  };
10
12
  export declare function applyActionWithFallbacks(page: Page, root: LocatorRoot, locator: Locator, action: BrowseAction, value: string | undefined, attempts: string[], family?: TargetControlFamily, options?: ActionExecutionOptions): Promise<boolean>;
11
13
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAc1E,KAAK,sBAAsB,GAAG;IAC5B,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,CAAC,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CAqDlB"}
1
+ {"version":3,"file":"action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAc5E,KAAK,sBAAsB,GAAG;IAC5B,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,CAAC,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CAsDlB"}
@@ -14,6 +14,7 @@ export async function applyActionWithFallbacks(page, root, locator, action, valu
14
14
  return applyTriggerAction(page, locator, attempts, {
15
15
  beforeRetry: options?.beforeClickRetry,
16
16
  guards: options?.guards,
17
+ clickActivationStrategy: options?.clickActivationStrategy,
17
18
  });
18
19
  }
19
20
  return applyEditableClickAction(page, locator, attempts, {
@@ -1,8 +1,10 @@
1
1
  import type { Locator, Page } from 'playwright-core';
2
+ import type { ClickActivationStrategy } from './click-activation-policy.js';
2
3
  import { type ActionExecutionGuards } from './action-execution-guards.js';
3
4
  type ClickRetryOptions = {
4
5
  beforeRetry?: () => Promise<void>;
5
6
  guards?: ActionExecutionGuards;
7
+ clickActivationStrategy?: ClickActivationStrategy;
6
8
  };
7
9
  export declare function applyEditableClickAction(page: Page, locator: Locator, attempts: string[], options?: ClickRetryOptions): Promise<boolean>;
8
10
  export declare function applyTriggerAction(page: Page, locator: Locator, attempts: string[], options?: ClickRetryOptions): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"click-action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/click-action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAMrD,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEnG,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC,CAAC;AA+FF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB"}
1
+ {"version":3,"file":"click-action-executor.d.ts","sourceRoot":"","sources":["../../src/commands/click-action-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAMrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEnG,KAAK,iBAAiB,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD,CAAC;AA2GF,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB"}
@@ -28,6 +28,17 @@ async function ensureLocatorRetryReady(locator, error, options) {
28
28
  }
29
29
  async function applyClickSequence(page, locator, attempts, options) {
30
30
  await handoffFocusFromActiveIframe(locator, attempts);
31
+ if (options?.clickActivationStrategy === 'dom') {
32
+ await runActionExecutionGuard(options?.guards, 'click.evaluate.primary');
33
+ attempts.push('locator.evaluate.click.primary');
34
+ await locator.evaluate((element) => {
35
+ if (!(element instanceof HTMLElement)) {
36
+ throw new Error('unsupported_js_click_fallback');
37
+ }
38
+ element.click();
39
+ });
40
+ return false;
41
+ }
31
42
  attempts.push('locator.click');
32
43
  try {
33
44
  await locator.click({ timeout: LOCATOR_CLICK_TIMEOUT_MS });
@@ -0,0 +1,5 @@
1
+ import type { TargetDescriptor } from '../runtime-state.js';
2
+ import type { BrowseAction } from './browse-actions.js';
3
+ export type ClickActivationStrategy = 'pointer' | 'dom';
4
+ export declare function clickActivationStrategyForTarget(target: Pick<TargetDescriptor, 'kind' | 'framePath' | 'semantics'>, action: BrowseAction): ClickActivationStrategy;
5
+ //# sourceMappingURL=click-activation-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"click-activation-policy.d.ts","sourceRoot":"","sources":["../../src/commands/click-activation-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,KAAK,CAAC;AAExD,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC,EAClE,MAAM,EAAE,YAAY,GACnB,uBAAuB,CAezB"}
@@ -0,0 +1,13 @@
1
+ export function clickActivationStrategyForTarget(target, action) {
2
+ if (action !== 'click') {
3
+ return 'pointer';
4
+ }
5
+ const withinIframe = Boolean(target.framePath?.length);
6
+ if (!withinIframe) {
7
+ return 'pointer';
8
+ }
9
+ const kind = target.kind?.trim().toLowerCase();
10
+ const role = target.semantics?.role?.trim().toLowerCase();
11
+ const isTab = kind === 'tab' || role === 'tab';
12
+ return isTab ? 'dom' : 'pointer';
13
+ }
@@ -1,5 +1,16 @@
1
1
  /**
2
2
  * browse close — Close the browser and clean up session.
3
3
  */
4
- export declare function close(): Promise<void>;
4
+ export type CloseSuccessResult = {
5
+ success: true;
6
+ };
7
+ export type CloseFailureResult = {
8
+ success: false;
9
+ error: 'browser_close_failed';
10
+ outcomeType: 'blocked';
11
+ message: 'Browser close failed.';
12
+ reason: string;
13
+ };
14
+ export type CloseResult = CloseSuccessResult | CloseFailureResult;
15
+ export declare function close(): Promise<CloseResult>;
5
16
  //# sourceMappingURL=close.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/commands/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,sBAAsB,CAAC;IAC9B,WAAW,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,uBAAuB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAMlE,wBAAsB,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAwBlD"}
@@ -1,32 +1,30 @@
1
1
  /**
2
2
  * browse close — Close the browser and clean up session.
3
3
  */
4
- import { execSync } from 'node:child_process';
5
- import { loadSession, deleteSession, getSessionPort } from '../session.js';
6
- import { outputJSON, info } from '../output.js';
4
+ import { loadSession, deleteSession } from '../session.js';
5
+ import { info } from '../output.js';
6
+ import { terminateOwnedPid } from '../owned-process.js';
7
+ function isOwnedSessionRecord(session) {
8
+ return session?.identity?.ownership === 'agentbrowse';
9
+ }
7
10
  export async function close() {
8
11
  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}`);
12
+ if (isOwnedSessionRecord(session)) {
13
+ const termination = await terminateOwnedPid(session.pid);
14
+ if (termination === 'still_alive') {
15
+ info(`Owned browser pid ${session.pid} did not exit; keeping session record`);
16
+ return {
17
+ success: false,
18
+ error: 'browser_close_failed',
19
+ outcomeType: 'blocked',
20
+ message: 'Browser close failed.',
21
+ reason: `Owned browser pid ${session.pid} did not exit after SIGTERM/SIGKILL.`,
22
+ };
23
23
  }
24
- }
25
- catch {
26
- /* no process on port */
24
+ info(`Killed owned browser pid ${session.pid}`);
27
25
  }
28
26
  if (session) {
29
27
  deleteSession();
30
28
  }
31
- outputJSON({ success: true });
29
+ return { success: true };
32
30
  }
@@ -1,10 +1,31 @@
1
1
  /**
2
2
  * browse launch [url] — Start browser session, optionally navigate.
3
3
  */
4
+ import type { SecretCatalogSummary } from '../secrets/catalog-sync.js';
4
5
  export type LaunchCliOptions = {
5
6
  compact?: boolean;
6
7
  profile?: string;
7
8
  headless?: boolean;
9
+ proxy?: string;
10
+ noProxy?: boolean;
8
11
  };
9
- export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<void>;
12
+ export type LaunchSuccessResult = {
13
+ success: true;
14
+ runtime: 'managed';
15
+ captchaSolveCapable: true;
16
+ profile: string;
17
+ cdpUrl: string;
18
+ url: string;
19
+ title: string;
20
+ secretCatalog?: SecretCatalogSummary;
21
+ };
22
+ export type LaunchFailureResult = {
23
+ success: false;
24
+ error: 'browser_launch_failed';
25
+ outcomeType: 'blocked';
26
+ message: 'Browser launch failed.';
27
+ reason: string;
28
+ };
29
+ export type LaunchResult = LaunchSuccessResult | LaunchFailureResult;
30
+ export declare function launch(url?: string, opts?: LaunchCliOptions): Promise<LaunchResult>;
10
31
  //# sourceMappingURL=launch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,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"}
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAWvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;IACnB,mBAAmB,EAAE,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;IAC/B,WAAW,EAAE,SAAS,CAAC;IACvB,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAErE,wBAAsB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAazF"}
@@ -1,92 +1,122 @@
1
1
  /**
2
2
  * browse launch [url] — Start browser session, optionally navigate.
3
3
  */
4
- import { execSync } from 'node:child_process';
5
4
  import { readConfig } from '../solver/config.js';
6
5
  import { ensureProfile } from '../solver/profile-manager.js';
7
6
  import { launchSolver } from '../solver/browser-launcher.js';
8
- import { saveSession } from '../session.js';
7
+ import { buildOwnedSession, isOwnedSession, loadSession, saveSession } from '../session.js';
9
8
  import { findChromePid } from '../stagehand.js';
10
- import { outputContractFailure, outputJSON } from '../output.js';
11
9
  import { applyAgentpayGatewayEnv, tryResolveAgentpayGatewayConfig } from '../agentpay-gateway.js';
12
10
  import { connectPlaywright, disconnectPlaywright, syncLaunchPage } from '../playwright-runtime.js';
13
11
  import { summarizeSecretCatalog, syncSecretCatalogForUrl } from '../secrets/catalog-sync.js';
14
- const CDP_PORT = 9222;
12
+ import { cleanupManagedBrowserPids, terminateOwnedPid } from '../owned-process.js';
13
+ import { info } from '../output.js';
15
14
  const DEFAULT_PROFILE = 'default';
16
15
  const COMPACT_WINDOW = {
17
16
  width: 1280,
18
17
  height: 900,
19
18
  };
20
19
  export async function launch(url, opts) {
21
- await killChromeOnPort(CDP_PORT);
20
+ const cleanupFailure = await cleanupOwnedSession();
21
+ if (cleanupFailure) {
22
+ return cleanupFailure;
23
+ }
22
24
  const compact = opts?.compact ?? true;
23
25
  const profileName = opts?.profile ?? DEFAULT_PROFILE;
24
26
  const headless = opts?.headless ?? false;
25
- await launchManaged(url, profileName, headless, compact);
27
+ const proxyOverride = opts?.proxy;
28
+ const noProxy = opts?.noProxy ?? false;
29
+ return launchManaged(url, profileName, headless, compact, proxyOverride, noProxy);
26
30
  }
27
- async function killChromeOnPort(port) {
28
- const pids = getPidsOnPort(port);
29
- if (pids.length === 0)
30
- return;
31
- for (const pid of pids) {
32
- try {
33
- process.kill(pid, 'SIGTERM');
34
- }
35
- catch {
36
- // Skip stale PID.
31
+ async function cleanupOwnedSession() {
32
+ const existingSession = loadSession();
33
+ const excludePids = new Set();
34
+ if (isOwnedSession(existingSession)) {
35
+ excludePids.add(existingSession.pid);
36
+ const termination = await terminateOwnedPid(existingSession.pid);
37
+ if (termination === 'still_alive') {
38
+ return buildLaunchFailure(new Error(`Existing owned browser pid ${existingSession.pid} did not exit after SIGTERM/SIGKILL.`));
37
39
  }
38
40
  }
39
- for (let i = 0; i < 20; i++) {
40
- if (getPidsOnPort(port).length === 0)
41
- return;
42
- await sleep(100);
41
+ // Phase-1 ownership removed port-global prekill, so launch needs a second guardrail
42
+ // for crashes that happen before a session record is persisted or cleaned up.
43
+ const orphanCleanup = await cleanupManagedBrowserPids({ excludePids });
44
+ if (orphanCleanup.blocked.length > 0) {
45
+ return buildLaunchFailure(new Error(`Orphaned managed browser pid${orphanCleanup.blocked.length === 1 ? '' : 's'} ${orphanCleanup.blocked.join(', ')} did not exit after SIGTERM/SIGKILL.`));
43
46
  }
44
- for (const pid of getPidsOnPort(port)) {
45
- try {
46
- process.kill(pid, 'SIGKILL');
47
- }
48
- catch {
49
- // Skip stale PID.
47
+ if (orphanCleanup.terminated.length > 0) {
48
+ info(`[launch] cleaned up orphaned managed browser pid${orphanCleanup.terminated.length === 1 ? '' : 's'} ${orphanCleanup.terminated.join(', ')}`);
49
+ }
50
+ return null;
51
+ }
52
+ function buildLaunchFailure(err) {
53
+ return {
54
+ success: false,
55
+ error: 'browser_launch_failed',
56
+ outcomeType: 'blocked',
57
+ message: 'Browser launch failed.',
58
+ reason: formatUnknownError(err),
59
+ };
60
+ }
61
+ function getPortFromCdpUrl(cdpUrl) {
62
+ try {
63
+ const url = new URL(cdpUrl);
64
+ if (!url.port) {
65
+ return undefined;
50
66
  }
67
+ const port = Number(url.port);
68
+ return Number.isFinite(port) && port > 0 ? port : undefined;
69
+ }
70
+ catch {
71
+ return undefined;
51
72
  }
52
73
  }
53
- async function launchManaged(url, profileName, headless, compact) {
74
+ async function launchManaged(url, profileName, headless, compact, proxyOverride, noProxy = false) {
54
75
  let session;
55
76
  let browser = null;
56
77
  let secretCatalogSummary;
57
78
  try {
58
- const profile = ensureProfile(profileName);
79
+ const baseProfile = ensureProfile(profileName);
59
80
  const config = readConfig();
81
+ const runtimeProxy = resolveLaunchProxy({
82
+ profileProxy: baseProfile.fingerprint.proxy,
83
+ configProxy: config.defaults?.proxy,
84
+ cliProxy: proxyOverride,
85
+ noProxy,
86
+ });
87
+ const profile = withLaunchProxy(baseProfile, runtimeProxy);
60
88
  const gateway = tryResolveAgentpayGatewayConfig();
61
89
  if (gateway) {
62
90
  applyAgentpayGatewayEnv(gateway);
63
91
  }
92
+ if (runtimeProxy) {
93
+ info(`[launch] starting browser with proxy ${runtimeProxy.server}`);
94
+ }
64
95
  session = await launchSolver(profile, {
65
96
  headless: headless || config.defaults?.headless,
66
97
  url,
67
- cdpPort: CDP_PORT,
98
+ cdpPort: undefined,
68
99
  windowSize: compact ? COMPACT_WINDOW : undefined,
69
100
  });
70
101
  }
71
102
  catch (err) {
72
- outputContractFailure({
73
- error: 'browser_launch_failed',
74
- outcomeType: 'blocked',
75
- message: 'Browser launch failed.',
76
- reason: formatUnknownError(err),
77
- });
103
+ return buildLaunchFailure(err);
104
+ }
105
+ const cdpPort = getPortFromCdpUrl(session.cdpUrl);
106
+ const chromePid = findChromePid(cdpPort);
107
+ if (!chromePid) {
108
+ await session.close().catch(() => undefined);
109
+ return buildLaunchFailure(new Error('Launched browser PID could not be resolved.'));
78
110
  }
79
- const chromePid = findChromePid(CDP_PORT) ?? process.pid;
80
- const persistedSession = {
111
+ const persistedSession = buildOwnedSession({
81
112
  cdpUrl: session.cdpUrl,
82
113
  pid: chromePid,
83
- port: CDP_PORT,
84
114
  profile: profileName,
85
115
  launchedAt: new Date().toISOString(),
86
116
  capabilities: {
87
117
  captchaSolve: true,
88
118
  },
89
- };
119
+ });
90
120
  saveSession(persistedSession);
91
121
  let currentUrl = session.page.url();
92
122
  let title = await session.page.title().catch(() => '');
@@ -111,10 +141,12 @@ async function launchManaged(url, profileName, headless, compact) {
111
141
  }
112
142
  if (url) {
113
143
  const snapshot = await syncSecretCatalogForUrl(persistedSession, currentUrl || url);
114
- secretCatalogSummary = summarizeSecretCatalog(snapshot);
144
+ if (snapshot) {
145
+ secretCatalogSummary = summarizeSecretCatalog(snapshot);
146
+ }
115
147
  }
116
148
  saveSession(persistedSession);
117
- outputJSON({
149
+ return {
118
150
  success: true,
119
151
  runtime: 'managed',
120
152
  captchaSolveCapable: true,
@@ -123,21 +155,7 @@ async function launchManaged(url, profileName, headless, compact) {
123
155
  url: currentUrl,
124
156
  title,
125
157
  secretCatalog: secretCatalogSummary,
126
- });
127
- }
128
- function getPidsOnPort(port) {
129
- try {
130
- const out = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
131
- if (!out)
132
- return [];
133
- return out
134
- .split('\n')
135
- .map((value) => Number(value))
136
- .filter((value) => Number.isFinite(value) && value > 0);
137
- }
138
- catch {
139
- return [];
140
- }
158
+ };
141
159
  }
142
160
  function formatUnknownError(err) {
143
161
  if (err instanceof Error)
@@ -154,6 +172,61 @@ function formatUnknownError(err) {
154
172
  }
155
173
  return String(err);
156
174
  }
157
- function sleep(ms) {
158
- return new Promise((resolve) => setTimeout(resolve, ms));
175
+ function resolveLaunchProxy(options) {
176
+ if (options.noProxy) {
177
+ return undefined;
178
+ }
179
+ if (options.cliProxy) {
180
+ return normalizeProxySetting(options.cliProxy);
181
+ }
182
+ if (options.configProxy) {
183
+ return normalizeProxySetting(options.configProxy);
184
+ }
185
+ if (options.profileProxy) {
186
+ return normalizeProxySetting(options.profileProxy);
187
+ }
188
+ return undefined;
189
+ }
190
+ function withLaunchProxy(profile, proxy) {
191
+ return {
192
+ ...profile,
193
+ fingerprint: {
194
+ ...profile.fingerprint,
195
+ proxy,
196
+ },
197
+ };
198
+ }
199
+ function normalizeProxySetting(value) {
200
+ if (typeof value === 'string') {
201
+ return parseProxyString(value);
202
+ }
203
+ const normalized = parseProxyString(value.server);
204
+ const username = value.username?.trim() || normalized.username;
205
+ const password = value.password ?? normalized.password;
206
+ return {
207
+ server: normalized.server,
208
+ ...(username ? { username } : {}),
209
+ ...(password ? { password } : {}),
210
+ };
211
+ }
212
+ function parseProxyString(value) {
213
+ const trimmed = value.trim();
214
+ if (!trimmed) {
215
+ throw new Error('Proxy value must not be empty.');
216
+ }
217
+ try {
218
+ const parsed = new URL(trimmed);
219
+ const server = `${parsed.protocol}//${parsed.host}`;
220
+ if (!parsed.hostname) {
221
+ throw new Error('missing hostname');
222
+ }
223
+ return {
224
+ server,
225
+ ...(parsed.username ? { username: decodeURIComponent(parsed.username) } : {}),
226
+ ...(parsed.password ? { password: decodeURIComponent(parsed.password) } : {}),
227
+ };
228
+ }
229
+ catch {
230
+ return { server: trimmed };
231
+ }
159
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAyCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAsCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,CA6F/F;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,uBAAuB,CAyBzB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,mBAAmB,GAAG,SAAS,CA0EjC;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,sBAAsB,GAAG,SAAS,CAiDpC"}
1
+ {"version":3,"file":"control-semantics.d.ts","sourceRoot":"","sources":["../src/control-semantics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAyCtF,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,oBAAoB,GAC1B,mBAAmB,GAAG,SAAS,CAuCjC;AAsCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAYxE;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,EAAE,CAoG/F;AAED,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC1B,GACL,uBAAuB,CAyBzB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,mBAAmB,GAAG,SAAS,CA0EjC;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,oBAAoB,EAC3B,cAAc,EAAE,aAAa,CAAC,mBAAmB,CAAC,GACjD,sBAAsB,GAAG,SAAS,CAiDpC"}
@@ -148,7 +148,12 @@ export function inferAllowedActionsFromFacts(facts) {
148
148
  actions.add('click');
149
149
  actions.add('press');
150
150
  }
151
- if (kind === 'button' || kind === 'link' || role === 'button' || role === 'link') {
151
+ if (kind === 'button' ||
152
+ kind === 'link' ||
153
+ kind === 'tab' ||
154
+ role === 'button' ||
155
+ role === 'link' ||
156
+ role === 'tab') {
152
157
  actions.add('click');
153
158
  actions.add('press');
154
159
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAiPA,iBAAe,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4JhE;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAWzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA0QA,iBAAe,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgKhE;AAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAWzF"}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ loadEnv();
6
6
  import { preflightAgentpayGateway } from './agentpay-gateway.js';
7
7
  import { browseCommand, browseCommandName } from './command-name.js';
8
8
  import { loadSession } from './session.js';
9
- import { outputError, fatal, info } from './output.js';
9
+ import { outputError, outputJSON, fatal, info } from './output.js';
10
10
  function usageText() {
11
11
  return `Usage: ${browseCommandName()} <command> [args] [options]
12
12
 
@@ -32,8 +32,10 @@ Options:
32
32
  --compact Launch browser in compact window size (1280x900, default)
33
33
  --full Launch browser in full-size window
34
34
  --profile <name> Solver profile name for launch (default: "default")
35
+ --proxy <url> Launch browser through proxy for this run only
36
+ --proxyless Ignore configured/profile proxy for this run only
37
+ --headful Explicit alias for headful browser mode (default)
35
38
  --headless Launch browser in headless mode
36
- --no-headless Explicit alias for visible browser mode
37
39
  --path <file> Output path for screenshot
38
40
  --timeout <seconds> Timeout for solve-captcha command (default: 90)
39
41
  --help Show this help message
@@ -113,13 +115,15 @@ function parseLaunchArgs(args) {
113
115
  let compact = true;
114
116
  let profile;
115
117
  let headless = false;
118
+ let proxy;
119
+ let noProxy = false;
116
120
  for (let i = 0; i < args.length; i++) {
117
121
  const arg = args[i];
118
122
  if (arg === '--headless') {
119
123
  headless = true;
120
124
  continue;
121
125
  }
122
- if (arg === '--no-headless') {
126
+ if (arg === '--headful') {
123
127
  headless = false;
124
128
  continue;
125
129
  }
@@ -134,21 +138,36 @@ function parseLaunchArgs(args) {
134
138
  if (arg === '--profile') {
135
139
  const value = args[i + 1];
136
140
  if (!value || value.startsWith('--')) {
137
- outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--headless]')}`);
141
+ outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
138
142
  }
139
143
  profile = value;
140
144
  i += 1;
141
145
  continue;
142
146
  }
147
+ if (arg === '--proxy') {
148
+ const value = args[i + 1];
149
+ if (!value || value.startsWith('--')) {
150
+ outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
151
+ }
152
+ proxy = value;
153
+ noProxy = false;
154
+ i += 1;
155
+ continue;
156
+ }
157
+ if (arg === '--proxyless') {
158
+ proxy = undefined;
159
+ noProxy = true;
160
+ continue;
161
+ }
143
162
  if (arg.startsWith('--')) {
144
163
  outputError(`Unknown launch option: ${arg}`);
145
164
  }
146
165
  if (url) {
147
- outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--headless]')}`);
166
+ outputError(`Usage: ${browseCommand('launch', '[url]', '[--profile <name>]', '[--proxy <url>|--proxyless]', '[--headful|--headless]')}`);
148
167
  }
149
168
  url = arg;
150
169
  }
151
- return { url, compact, profile, headless };
170
+ return { url, compact, profile, headless, proxy, noProxy };
152
171
  }
153
172
  function parseCaptchaTimeout(args) {
154
173
  const timeoutRaw = getFlag(args, '--timeout');
@@ -220,11 +239,13 @@ async function main(argv = process.argv) {
220
239
  case 'launch': {
221
240
  const { launch } = await import('./commands/launch.js');
222
241
  const launchArgs = parseLaunchArgs(args);
223
- await launch(launchArgs.url, {
242
+ outputJSON(await launch(launchArgs.url, {
224
243
  compact: launchArgs.compact,
225
244
  profile: launchArgs.profile,
226
245
  headless: launchArgs.headless,
227
- });
246
+ proxy: launchArgs.proxy,
247
+ noProxy: launchArgs.noProxy,
248
+ }));
228
249
  break;
229
250
  }
230
251
  case 'navigate': {
@@ -322,7 +343,7 @@ async function main(argv = process.argv) {
322
343
  }
323
344
  case 'close': {
324
345
  const { close } = await import('./commands/close.js');
325
- await close();
346
+ outputJSON(await close());
326
347
  break;
327
348
  }
328
349
  }
@@ -0,0 +1,14 @@
1
+ export type OwnedPidTerminationResult = 'not_found' | 'terminated' | 'sigkilled' | 'still_alive';
2
+ export type ManagedBrowserCleanupResult = {
3
+ terminated: number[];
4
+ blocked: number[];
5
+ };
6
+ export declare function isPidAlive(pid: number): boolean;
7
+ export declare function terminateOwnedPid(pid: number): Promise<OwnedPidTerminationResult>;
8
+ export declare function cleanupManagedBrowserPids(options?: {
9
+ excludePids?: Iterable<number>;
10
+ }): Promise<ManagedBrowserCleanupResult>;
11
+ export declare function listManagedBrowserPids(options?: {
12
+ processTable?: string;
13
+ }): number[];
14
+ //# sourceMappingURL=owned-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owned-process.d.ts","sourceRoot":"","sources":["../src/owned-process.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,yBAAyB,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,aAAa,CAAC;AACjG,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW/C;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsBvF;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,OAAO,CAAC,2BAA2B,CAAC,CAqBtC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,MAAM,EAAE,CAuBV"}
@@ -0,0 +1,131 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ const TERM_WAIT_ATTEMPTS = 10;
5
+ const TERM_WAIT_MS = 100;
6
+ const KILL_WAIT_ATTEMPTS = 5;
7
+ const KILL_WAIT_MS = 20;
8
+ const MANAGED_PROFILE_ROOTS = [
9
+ path.join(homedir(), '.agentpay', 'agentbrowse', 'profiles'),
10
+ path.join(homedir(), '.agentpay', 'solver', 'profiles'),
11
+ ];
12
+ export function isPidAlive(pid) {
13
+ try {
14
+ process.kill(pid, 0);
15
+ return true;
16
+ }
17
+ catch (error) {
18
+ const code = getErrorCode(error);
19
+ if (code === 'EPERM') {
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ }
25
+ export async function terminateOwnedPid(pid) {
26
+ try {
27
+ process.kill(pid, 'SIGTERM');
28
+ }
29
+ catch (error) {
30
+ return isPidAlive(pid) ? 'still_alive' : 'not_found';
31
+ }
32
+ if (await waitForPidExit(pid, TERM_WAIT_ATTEMPTS, TERM_WAIT_MS)) {
33
+ return 'terminated';
34
+ }
35
+ try {
36
+ process.kill(pid, 'SIGKILL');
37
+ }
38
+ catch {
39
+ return isPidAlive(pid) ? 'still_alive' : 'sigkilled';
40
+ }
41
+ if (await waitForPidExit(pid, KILL_WAIT_ATTEMPTS, KILL_WAIT_MS)) {
42
+ return 'sigkilled';
43
+ }
44
+ return 'still_alive';
45
+ }
46
+ export async function cleanupManagedBrowserPids(options = {}) {
47
+ const excludePids = new Set(options.excludePids ?? []);
48
+ const terminated = [];
49
+ const blocked = [];
50
+ for (const pid of listManagedBrowserPids()) {
51
+ if (excludePids.has(pid)) {
52
+ continue;
53
+ }
54
+ const result = await terminateOwnedPid(pid);
55
+ if (result === 'still_alive') {
56
+ blocked.push(pid);
57
+ continue;
58
+ }
59
+ if (result !== 'not_found') {
60
+ terminated.push(pid);
61
+ }
62
+ }
63
+ return { terminated, blocked };
64
+ }
65
+ export function listManagedBrowserPids(options = {}) {
66
+ const processTable = options.processTable ?? readProcessTable();
67
+ const pids = new Set();
68
+ for (const line of processTable.split(/\r?\n/)) {
69
+ const match = line.trimStart().match(/^(\d+)\s+(.*)$/);
70
+ if (!match) {
71
+ continue;
72
+ }
73
+ const pid = Number(match[1]);
74
+ const command = match[2] ?? '';
75
+ if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) {
76
+ continue;
77
+ }
78
+ if (!isManagedBrowserCommand(command)) {
79
+ continue;
80
+ }
81
+ pids.add(pid);
82
+ }
83
+ return Array.from(pids);
84
+ }
85
+ async function waitForPidExit(pid, attempts, intervalMs) {
86
+ for (let attempt = 0; attempt < attempts; attempt++) {
87
+ if (!isPidAlive(pid)) {
88
+ return true;
89
+ }
90
+ await sleep(intervalMs);
91
+ }
92
+ return !isPidAlive(pid);
93
+ }
94
+ function getErrorCode(error) {
95
+ if (!error || typeof error !== 'object') {
96
+ return undefined;
97
+ }
98
+ const code = Reflect.get(error, 'code');
99
+ return typeof code === 'string' ? code : undefined;
100
+ }
101
+ function sleep(ms) {
102
+ return new Promise((resolve) => setTimeout(resolve, ms));
103
+ }
104
+ function readProcessTable() {
105
+ try {
106
+ return execFileSync('ps', ['-Ao', 'pid=,command='], { encoding: 'utf-8' });
107
+ }
108
+ catch {
109
+ return '';
110
+ }
111
+ }
112
+ function isManagedBrowserCommand(command) {
113
+ if (!command.includes('--remote-debugging-address=127.0.0.1')) {
114
+ return false;
115
+ }
116
+ const userDataDir = readCommandFlag(command, '--user-data-dir');
117
+ if (!userDataDir) {
118
+ return false;
119
+ }
120
+ const normalizedUserDataDir = path.resolve(userDataDir);
121
+ return MANAGED_PROFILE_ROOTS.some((root) => isWithinRoot(normalizedUserDataDir, root));
122
+ }
123
+ function readCommandFlag(command, flag) {
124
+ const escapedFlag = flag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
+ const match = command.match(new RegExp(`${escapedFlag}=(?:"([^"]+)"|'([^']+)'|(\\S+))`));
126
+ return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
127
+ }
128
+ function isWithinRoot(candidatePath, rootPath) {
129
+ const relative = path.relative(rootPath, candidatePath);
130
+ return relative.length > 0 && !relative.startsWith(`..${path.sep}`) && relative !== '..';
131
+ }
package/dist/session.d.ts CHANGED
@@ -19,12 +19,21 @@ export interface BrowseSession {
19
19
  launchedAt: string;
20
20
  port?: number;
21
21
  profile?: string;
22
+ identity?: BrowseSessionIdentity;
22
23
  capabilities?: {
23
24
  captchaSolve?: boolean;
24
25
  };
25
26
  runtime?: BrowseRuntimeState;
26
27
  transientSecretCache?: Record<string, CachedTransientSecretEntry>;
27
28
  }
29
+ export interface BrowseSessionIdentity {
30
+ browserInstanceRef: string;
31
+ endpoint: string;
32
+ pid: number;
33
+ profile?: string;
34
+ launchedAt: string;
35
+ ownership: 'agentbrowse';
36
+ }
28
37
  export declare function cleanupTransientSecretCache(session: BrowseSession, options?: {
29
38
  now?: string;
30
39
  }): boolean;
@@ -34,11 +43,15 @@ export declare function getCachedTransientSecret(session: BrowseSession, intentI
34
43
  }): CachedTransientSecretEntry | null;
35
44
  export declare function deleteCachedTransientSecret(session: BrowseSession, intentId: string): boolean;
36
45
  export declare function serializeSession(session: BrowseSession): string;
46
+ export declare function buildOwnedSession(session: Omit<BrowseSession, 'port' | 'identity'>): BrowseSession;
37
47
  export declare function saveSession(session: BrowseSession): void;
38
48
  export declare function loadSession(): BrowseSession | null;
39
49
  export declare function getSessionPort(session: BrowseSession | null): number;
40
50
  export declare function deleteSession(): void;
41
51
  export declare function supportsCaptchaSolve(session: BrowseSession | null): boolean;
52
+ export declare function isOwnedSession(session: BrowseSession | null | undefined): session is BrowseSession & {
53
+ identity: BrowseSessionIdentity;
54
+ };
42
55
  /** Check if the Chrome process from the session is still alive. */
43
56
  export declare function isSessionAlive(session: BrowseSession): boolean;
44
57
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;CACnE;AAyBD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAqBT;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,0BAA0B,GAChC,0BAA0B,CAQ5B;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,0BAA0B,GAAG,IAAI,CAMnC;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAW7F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAW/D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAGxD;AAED,wBAAgB,WAAW,IAAI,aAAa,GAAG,IAAI,CAclD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAapE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAE3E;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAO9D"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAK/D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,YAAY,CAAC,EAAE;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,aAAa,CAAC;CAC1B;AAkDD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAqBT;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,0BAA0B,GAChC,0BAA0B,CAQ5B;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,0BAA0B,GAAG,IAAI,CAMnC;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAW7F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAW/D;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,CAAC,GAChD,aAAa,CAaf;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAGxD;AAED,wBAAgB,WAAW,IAAI,aAAa,GAAG,IAAI,CAclD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAWpE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAE3E;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GACxC,OAAO,IAAI,aAAa,GAAG;IAAE,QAAQ,EAAE,qBAAqB,CAAA;CAAE,CAahE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAO9D"}
package/dist/session.js CHANGED
@@ -20,6 +20,30 @@ function ensureDir() {
20
20
  mkdirSync(SESSION_DIR, { recursive: true });
21
21
  }
22
22
  }
23
+ function parseSessionPort(cdpUrl) {
24
+ try {
25
+ const url = new URL(cdpUrl);
26
+ if (!url.port) {
27
+ return undefined;
28
+ }
29
+ const port = Number(url.port);
30
+ return Number.isFinite(port) && port > 0 ? port : undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ function parseBrowserInstanceRef(cdpUrl) {
37
+ try {
38
+ const url = new URL(cdpUrl);
39
+ const pathSegments = url.pathname.split('/').filter(Boolean);
40
+ return pathSegments.at(-1) ?? cdpUrl;
41
+ }
42
+ catch {
43
+ const pathSegments = cdpUrl.split('/').filter(Boolean);
44
+ return pathSegments.at(-1) ?? cdpUrl;
45
+ }
46
+ }
23
47
  function isExpired(expiresAt, now) {
24
48
  if (!expiresAt) {
25
49
  return false;
@@ -79,6 +103,20 @@ export function serializeSession(session) {
79
103
  return value;
80
104
  }, 2);
81
105
  }
106
+ export function buildOwnedSession(session) {
107
+ return {
108
+ ...session,
109
+ port: parseSessionPort(session.cdpUrl),
110
+ identity: {
111
+ browserInstanceRef: parseBrowserInstanceRef(session.cdpUrl),
112
+ endpoint: session.cdpUrl,
113
+ pid: session.pid,
114
+ profile: session.profile,
115
+ launchedAt: session.launchedAt,
116
+ ownership: 'agentbrowse',
117
+ },
118
+ };
119
+ }
82
120
  export function saveSession(session) {
83
121
  ensureDir();
84
122
  writeFileSync(SESSION_PATH, serializeSession(session));
@@ -105,13 +143,9 @@ export function getSessionPort(session) {
105
143
  if (session?.port)
106
144
  return session.port;
107
145
  if (session?.cdpUrl) {
108
- try {
109
- const url = new URL(session.cdpUrl);
110
- if (url.port)
111
- return Number(url.port);
112
- }
113
- catch {
114
- // Ignore malformed cdpUrl and use default fallback.
146
+ const parsedPort = parseSessionPort(session.cdpUrl);
147
+ if (parsedPort) {
148
+ return parsedPort;
115
149
  }
116
150
  }
117
151
  return 9222;
@@ -123,6 +157,17 @@ export function deleteSession() {
123
157
  export function supportsCaptchaSolve(session) {
124
158
  return session?.capabilities?.captchaSolve === true;
125
159
  }
160
+ export function isOwnedSession(session) {
161
+ if (!session?.identity) {
162
+ return false;
163
+ }
164
+ return (session.identity.ownership === 'agentbrowse' &&
165
+ session.identity.endpoint === session.cdpUrl &&
166
+ session.identity.pid === session.pid &&
167
+ session.identity.launchedAt === session.launchedAt &&
168
+ session.identity.browserInstanceRef.length > 0 &&
169
+ session.identity.profile === session.profile);
170
+ }
126
171
  /** Check if the Chrome process from the session is still alive. */
127
172
  export function isSessionAlive(session) {
128
173
  try {
@@ -90,14 +90,9 @@ async function discoverCdpUrl(options) {
90
90
  }
91
91
  else {
92
92
  const activePort = readDevToolsActivePort(activePortPath);
93
- if (activePort?.browserWSEndpoint) {
94
- return activePort.browserWSEndpoint;
95
- }
96
- if (activePort?.port) {
97
- const cdpUrl = await discoverCdpUrlOnPort(activePort.port);
98
- if (cdpUrl) {
99
- return cdpUrl;
100
- }
93
+ const discoveredViaActivePort = await discoverCdpUrlFromActivePort(activePort);
94
+ if (discoveredViaActivePort) {
95
+ return discoveredViaActivePort;
101
96
  }
102
97
  }
103
98
  await sleep(CDP_DISCOVERY_INTERVAL_MS);
@@ -117,6 +112,38 @@ async function discoverCdpUrlOnPort(port) {
117
112
  return null;
118
113
  }
119
114
  }
115
+ async function discoverCdpUrlFromActivePort(activePort) {
116
+ if (!activePort) {
117
+ return null;
118
+ }
119
+ const candidatePorts = new Set();
120
+ if (activePort.browserWSEndpoint) {
121
+ const wsPort = portFromBrowserWSEndpoint(activePort.browserWSEndpoint);
122
+ if (wsPort) {
123
+ candidatePorts.add(wsPort);
124
+ }
125
+ }
126
+ if (activePort.port > 0) {
127
+ candidatePorts.add(activePort.port);
128
+ }
129
+ for (const port of candidatePorts) {
130
+ const cdpUrl = await discoverCdpUrlOnPort(port);
131
+ if (cdpUrl) {
132
+ return cdpUrl;
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ function portFromBrowserWSEndpoint(browserWSEndpoint) {
138
+ try {
139
+ const url = new URL(browserWSEndpoint);
140
+ const port = Number(url.port);
141
+ return Number.isFinite(port) && port > 0 ? port : null;
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
120
147
  function readDevToolsActivePort(activePortPath) {
121
148
  if (!existsSync(activePortPath)) {
122
149
  return null;
@@ -24,6 +24,7 @@ export type ProxyConfig = {
24
24
  username?: string;
25
25
  password?: string;
26
26
  };
27
+ export type ProxySetting = string | ProxyConfig;
27
28
  export type ProfileInfo = {
28
29
  name: string;
29
30
  fingerprint: BrowserFingerprint;
@@ -37,6 +38,7 @@ export type SolverConfig = {
37
38
  };
38
39
  defaults?: {
39
40
  headless?: boolean;
41
+ proxy?: ProxySetting;
40
42
  };
41
43
  };
42
44
  export type CaptchaType = 'recaptcha-v2' | 'hcaptcha' | 'turnstile';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/solver/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,sBAAsB,CAAC;AACrE,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,UAAU,GAAG,cAAc,CAAC;AAEjF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/solver/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,CAAC;AAEhD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,YAAY,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,sBAAsB,CAAC;AACrE,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,UAAU,GAAG,cAAc,CAAC;AAEjF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuanu-ai/agentbrowse",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "type": "module",
5
5
  "description": "Browser automation CLI for AI agents: control a CDP browser, observe UI surfaces, act on refs, extract data, capture screenshots, complete protected fills, and solve captchas",
6
6
  "keywords": [