browser-use 0.5.0 → 0.6.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 (55) hide show
  1. package/dist/agent/service.js +2 -0
  2. package/dist/agent/system_prompt.md +269 -0
  3. package/dist/agent/system_prompt_anthropic_flash.md +240 -0
  4. package/dist/agent/system_prompt_browser_use.md +18 -0
  5. package/dist/agent/system_prompt_browser_use_flash.md +15 -0
  6. package/dist/agent/system_prompt_browser_use_no_thinking.md +17 -0
  7. package/dist/agent/system_prompt_flash.md +16 -0
  8. package/dist/agent/system_prompt_flash_anthropic.md +30 -0
  9. package/dist/agent/system_prompt_no_thinking.md +245 -0
  10. package/dist/browser/cloud/index.d.ts +1 -0
  11. package/dist/browser/cloud/index.js +1 -0
  12. package/dist/browser/cloud/management.d.ts +130 -0
  13. package/dist/browser/cloud/management.js +140 -0
  14. package/dist/browser/events.d.ts +61 -3
  15. package/dist/browser/events.js +66 -0
  16. package/dist/browser/profile.d.ts +1 -0
  17. package/dist/browser/profile.js +1 -0
  18. package/dist/browser/session.d.ts +56 -2
  19. package/dist/browser/session.js +596 -24
  20. package/dist/browser/watchdogs/base.js +34 -1
  21. package/dist/browser/watchdogs/captcha-watchdog.d.ts +26 -0
  22. package/dist/browser/watchdogs/captcha-watchdog.js +151 -0
  23. package/dist/browser/watchdogs/index.d.ts +1 -0
  24. package/dist/browser/watchdogs/index.js +1 -0
  25. package/dist/browser/watchdogs/screenshot-watchdog.js +4 -3
  26. package/dist/cli.d.ts +120 -0
  27. package/dist/cli.js +1816 -4
  28. package/dist/controller/service.js +106 -362
  29. package/dist/controller/views.d.ts +9 -6
  30. package/dist/controller/views.js +8 -5
  31. package/dist/filesystem/file-system.js +1 -1
  32. package/dist/llm/litellm/chat.d.ts +11 -0
  33. package/dist/llm/litellm/chat.js +16 -0
  34. package/dist/llm/litellm/index.d.ts +1 -0
  35. package/dist/llm/litellm/index.js +1 -0
  36. package/dist/llm/models.js +29 -3
  37. package/dist/llm/oci-raw/chat.d.ts +64 -0
  38. package/dist/llm/oci-raw/chat.js +350 -0
  39. package/dist/llm/oci-raw/index.d.ts +2 -0
  40. package/dist/llm/oci-raw/index.js +2 -0
  41. package/dist/llm/oci-raw/serializer.d.ts +12 -0
  42. package/dist/llm/oci-raw/serializer.js +128 -0
  43. package/dist/mcp/server.d.ts +1 -0
  44. package/dist/mcp/server.js +62 -13
  45. package/dist/skill-cli/direct.d.ts +100 -0
  46. package/dist/skill-cli/direct.js +984 -0
  47. package/dist/skill-cli/index.d.ts +2 -0
  48. package/dist/skill-cli/index.js +2 -0
  49. package/dist/skill-cli/server.d.ts +2 -0
  50. package/dist/skill-cli/server.js +472 -11
  51. package/dist/skill-cli/tunnel.d.ts +61 -0
  52. package/dist/skill-cli/tunnel.js +257 -0
  53. package/dist/sync/auth.d.ts +8 -0
  54. package/dist/sync/auth.js +12 -0
  55. package/package.json +22 -4
package/dist/cli.js CHANGED
@@ -2,12 +2,16 @@
2
2
  import { promises as fs } from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'node:module';
5
7
  import { stdin, stdout } from 'node:process';
6
8
  import { createInterface } from 'node:readline/promises';
7
9
  import { Agent } from './agent/service.js';
8
10
  import { BrowserProfile, } from './browser/profile.js';
9
- import { BrowserSession } from './browser/session.js';
11
+ import { BrowserSession, systemChrome } from './browser/session.js';
10
12
  import { CONFIG } from './config.js';
13
+ import { CloudBrowserClient } from './browser/cloud/cloud.js';
14
+ import { CloudManagementClient, } from './browser/cloud/management.js';
11
15
  import { ChatOpenAI } from './llm/openai/chat.js';
12
16
  import { ChatAnthropic } from './llm/anthropic/chat.js';
13
17
  import { ChatGoogle } from './llm/google/chat.js';
@@ -17,6 +21,7 @@ import { ChatOpenRouter } from './llm/openrouter/chat.js';
17
21
  import { ChatAzure } from './llm/azure/chat.js';
18
22
  import { ChatOllama } from './llm/ollama/chat.js';
19
23
  import { ChatMistral } from './llm/mistral/chat.js';
24
+ import { ChatOCIRaw } from './llm/oci-raw/chat.js';
20
25
  import { ChatCerebras } from './llm/cerebras/chat.js';
21
26
  import { ChatVercel } from './llm/vercel/chat.js';
22
27
  import { ChatAnthropicBedrock } from './llm/aws/chat-anthropic.js';
@@ -25,8 +30,11 @@ import { ChatBrowserUse } from './llm/browser-use/chat.js';
25
30
  import { MCPServer } from './mcp/server.js';
26
31
  import { get_browser_use_version } from './utils.js';
27
32
  import { setupLogging } from './logging-config.js';
33
+ import { get_tunnel_manager } from './skill-cli/tunnel.js';
34
+ import { DeviceAuthClient, save_cloud_api_token } from './sync/auth.js';
28
35
  import dotenv from 'dotenv';
29
36
  dotenv.config();
37
+ const require = createRequire(import.meta.url);
30
38
  const CLI_PROVIDER_ALIASES = {
31
39
  openai: 'openai',
32
40
  anthropic: 'anthropic',
@@ -128,6 +136,10 @@ export const parseCliArgs = (argv) => {
128
136
  provider: null,
129
137
  prompt: null,
130
138
  mcp: false,
139
+ json: false,
140
+ yes: false,
141
+ setup_mode: null,
142
+ api_key: null,
131
143
  positional: [],
132
144
  };
133
145
  for (let i = 0; i < argv.length; i += 1) {
@@ -156,6 +168,14 @@ export const parseCliArgs = (argv) => {
156
168
  parsed.mcp = true;
157
169
  continue;
158
170
  }
171
+ if (arg === '--json') {
172
+ parsed.json = true;
173
+ continue;
174
+ }
175
+ if (arg === '-y' || arg === '--yes') {
176
+ parsed.yes = true;
177
+ continue;
178
+ }
159
179
  if (arg === '-p' || arg === '--prompt' || arg.startsWith('--prompt=')) {
160
180
  const { value, nextIndex } = takeOptionValue(arg, i, argv);
161
181
  parsed.prompt = value;
@@ -174,6 +194,18 @@ export const parseCliArgs = (argv) => {
174
194
  i = nextIndex;
175
195
  continue;
176
196
  }
197
+ if (arg === '--mode' || arg.startsWith('--mode=')) {
198
+ const { value, nextIndex } = takeOptionValue(arg, i, argv);
199
+ parsed.setup_mode = value.trim();
200
+ i = nextIndex;
201
+ continue;
202
+ }
203
+ if (arg === '--api-key' || arg.startsWith('--api-key=')) {
204
+ const { value, nextIndex } = takeOptionValue(arg, i, argv);
205
+ parsed.api_key = value.trim();
206
+ i = nextIndex;
207
+ continue;
208
+ }
177
209
  if (arg === '--window-width' || arg.startsWith('--window-width=')) {
178
210
  const { value, nextIndex } = takeOptionValue(arg, i, argv);
179
211
  parsed.window_width = parsePositiveInt('--window-width', value);
@@ -512,7 +544,9 @@ const createLlmForProvider = (provider, model) => {
512
544
  baseURL: process.env.VERCEL_BASE_URL,
513
545
  });
514
546
  case 'oci':
515
- throw new Error('OCI models require manual configuration in TypeScript runtime. Use a custom BaseChatModel integration for OCI credentials and endpoint setup.');
547
+ return new ChatOCIRaw({
548
+ model,
549
+ });
516
550
  case 'ollama': {
517
551
  const host = process.env.OLLAMA_HOST || 'http://localhost:11434';
518
552
  return new ChatOllama(model, host);
@@ -741,6 +775,14 @@ const runInteractiveMode = async (args, llm) => {
741
775
  };
742
776
  export const getCliUsage = () => `Usage:
743
777
  browser-use # interactive mode (TTY)
778
+ browser-use doctor
779
+ browser-use install
780
+ browser-use setup [--mode <local|remote|full>]
781
+ browser-use tunnel <port>
782
+ browser-use task <list|status|stop|logs>
783
+ browser-use session <list|get|stop|create|share>
784
+ browser-use profile <list|get|create|update|delete|cookies|sync>
785
+ browser-use run --remote <task>
744
786
  browser-use <task>
745
787
  browser-use -p "<task>"
746
788
  browser-use [options] <task>
@@ -750,9 +792,13 @@ Options:
750
792
  -h, --help Show this help message
751
793
  --version Print version and exit
752
794
  --mcp Run as MCP server
795
+ --json Output command results as JSON when supported
796
+ -y, --yes Skip optional setup prompts where supported
753
797
  --provider <name> Force provider (openai|anthropic|google|deepseek|groq|openrouter|azure|mistral|cerebras|vercel|oci|ollama|browser-use|aws|aws-anthropic)
754
798
  --model <model> Set model (e.g., gpt-5-mini, claude-4-sonnet, gemini-2.5-pro)
755
799
  -p, --prompt <task> Run a single task
800
+ --mode <name> Setup mode for setup command (local|remote|full)
801
+ --api-key <value> Browser Use API key for setup or cloud operations
756
802
  --headless Run browser in headless mode
757
803
  --allowed-domains <items> Comma-separated allowlist (e.g., example.com,*.example.org)
758
804
  --window-width <px> Browser window width
@@ -765,6 +811,1696 @@ Options:
765
811
  --proxy-password <value> Proxy password
766
812
  --cdp-url <url> Connect to an existing Chromium instance via CDP
767
813
  --debug Enable debug logging`;
814
+ const resolvePlaywrightCliPath = () => require.resolve('playwright/cli');
815
+ export const runInstallCommand = (options = {}) => {
816
+ const playwrightCliPath = options.playwright_cli_path ?? resolvePlaywrightCliPath();
817
+ const spawnImpl = options.spawn_impl ?? spawnSync;
818
+ const result = spawnImpl(process.execPath, [playwrightCliPath, 'install', 'chromium'], {
819
+ stdio: 'inherit',
820
+ });
821
+ if (result.error) {
822
+ throw result.error;
823
+ }
824
+ if (result.status !== 0) {
825
+ throw new Error(`Playwright browser install failed with exit code ${result.status ?? 1}`);
826
+ }
827
+ };
828
+ const writeLine = (stream, value) => {
829
+ stream.write(`${value}\n`);
830
+ };
831
+ const parseTunnelPort = (value) => {
832
+ const port = Number.parseInt(String(value ?? ''), 10);
833
+ if (!Number.isFinite(port) || port <= 0) {
834
+ throw new Error(`Invalid port: ${value ?? ''}`);
835
+ }
836
+ return port;
837
+ };
838
+ export const runTunnelCommand = async (argv, options = {}) => {
839
+ const manager = options.manager ?? get_tunnel_manager();
840
+ const output = options.stdout ?? process.stdout;
841
+ const errorOutput = options.stderr ?? process.stderr;
842
+ const json_output = Boolean(options.json_output);
843
+ const render = (value) => {
844
+ if (json_output) {
845
+ writeLine(output, JSON.stringify(value, null, 2));
846
+ }
847
+ };
848
+ try {
849
+ const first = argv[0] ?? null;
850
+ if (!first) {
851
+ writeLine(errorOutput, 'Usage: browser-use tunnel <port> | list | stop <port> | stop --all');
852
+ return 1;
853
+ }
854
+ if (first === 'list') {
855
+ const result = manager.list_tunnels();
856
+ if (json_output) {
857
+ render(result);
858
+ }
859
+ else if (result.tunnels.length > 0) {
860
+ for (const tunnel of result.tunnels) {
861
+ writeLine(output, `${tunnel.port}: ${tunnel.url}`);
862
+ }
863
+ }
864
+ else {
865
+ writeLine(output, 'No active tunnels');
866
+ }
867
+ return 0;
868
+ }
869
+ if (first === 'stop' || first === 'stop-all') {
870
+ const stopAll = first === 'stop-all' || argv.includes('--all');
871
+ if (stopAll) {
872
+ const result = await manager.stop_all_tunnels();
873
+ if (json_output) {
874
+ render(result);
875
+ }
876
+ else if (result.count > 0) {
877
+ writeLine(output, `Stopped ${result.count} tunnel(s): ${result.stopped.join(', ')}`);
878
+ }
879
+ else {
880
+ writeLine(output, 'No tunnels to stop');
881
+ }
882
+ return 0;
883
+ }
884
+ const port = parseTunnelPort(argv[1]);
885
+ const result = await manager.stop_tunnel(port);
886
+ if ('error' in result) {
887
+ writeLine(errorOutput, result.error);
888
+ return 1;
889
+ }
890
+ if (json_output) {
891
+ render(result);
892
+ }
893
+ else {
894
+ writeLine(output, `Stopped tunnel on port ${result.stopped}`);
895
+ }
896
+ return 0;
897
+ }
898
+ const port = parseTunnelPort(first);
899
+ const result = await manager.start_tunnel(port);
900
+ if ('error' in result) {
901
+ writeLine(errorOutput, result.error);
902
+ return 1;
903
+ }
904
+ if (json_output) {
905
+ render(result);
906
+ }
907
+ else if (result.existing) {
908
+ writeLine(output, `Tunnel already running: http://localhost:${result.port} -> ${result.url}`);
909
+ }
910
+ else {
911
+ writeLine(output, `Tunnel started: http://localhost:${result.port} -> ${result.url}`);
912
+ }
913
+ return 0;
914
+ }
915
+ catch (error) {
916
+ writeLine(errorOutput, error.message);
917
+ return 1;
918
+ }
919
+ };
920
+ const validateSetupMode = (mode) => {
921
+ const normalized = (mode ?? 'local').trim().toLowerCase();
922
+ if (normalized === 'local' ||
923
+ normalized === 'remote' ||
924
+ normalized === 'full') {
925
+ return normalized;
926
+ }
927
+ throw new Error(`Invalid setup mode "${mode ?? ''}". Expected local, remote, or full.`);
928
+ };
929
+ const renderSetupChecks = (mode, report) => {
930
+ const checks = {
931
+ browser_use_package: report.checks.package,
932
+ };
933
+ if (mode === 'local' || mode === 'full') {
934
+ checks.browser = report.checks.browser;
935
+ }
936
+ if (mode === 'remote' || mode === 'full') {
937
+ checks.api_key = report.checks.api_key;
938
+ checks.cloudflared = report.checks.cloudflared;
939
+ }
940
+ return checks;
941
+ };
942
+ const planSetupActions = (mode, checks, yes, api_key) => {
943
+ const actions = [];
944
+ if ((mode === 'local' || mode === 'full') &&
945
+ checks.browser?.status !== 'ok') {
946
+ actions.push({
947
+ type: 'install_browser',
948
+ description: 'Install browser (Chromium)',
949
+ required: true,
950
+ });
951
+ }
952
+ if ((mode === 'remote' || mode === 'full') &&
953
+ checks.api_key?.status !== 'ok') {
954
+ if (api_key?.trim()) {
955
+ actions.push({
956
+ type: 'configure_api_key',
957
+ description: 'Configure API key',
958
+ required: true,
959
+ api_key: api_key.trim(),
960
+ });
961
+ }
962
+ else if (!yes) {
963
+ actions.push({
964
+ type: 'prompt_api_key',
965
+ description: 'Prompt for API key',
966
+ required: false,
967
+ });
968
+ }
969
+ }
970
+ if ((mode === 'remote' || mode === 'full') &&
971
+ checks.cloudflared?.status !== 'ok') {
972
+ actions.push({
973
+ type: 'install_cloudflared',
974
+ description: 'Install cloudflared (for tunneling)',
975
+ required: true,
976
+ });
977
+ }
978
+ return actions;
979
+ };
980
+ const logSetupChecks = (stream, checks) => {
981
+ writeLine(stream, '');
982
+ writeLine(stream, 'Running checks...');
983
+ writeLine(stream, '');
984
+ for (const [name, check] of Object.entries(checks)) {
985
+ const icon = check.status === 'ok' ? '✓' : check.status === 'missing' ? '⚠' : '✗';
986
+ writeLine(stream, ` ${icon} ${name.replace(/_/g, ' ')}: ${check.message}`);
987
+ }
988
+ writeLine(stream, '');
989
+ };
990
+ const logSetupActions = (stream, actions) => {
991
+ if (actions.length === 0) {
992
+ writeLine(stream, 'No additional setup needed.');
993
+ writeLine(stream, '');
994
+ return;
995
+ }
996
+ writeLine(stream, '');
997
+ writeLine(stream, 'Setup actions:');
998
+ writeLine(stream, '');
999
+ actions.forEach((action, index) => {
1000
+ writeLine(stream, ` ${index + 1}. ${action.description} ${action.required ? '(required)' : '(optional)'}`);
1001
+ });
1002
+ writeLine(stream, '');
1003
+ };
1004
+ const logSetupValidation = (stream, validation) => {
1005
+ writeLine(stream, '');
1006
+ writeLine(stream, 'Validation:');
1007
+ writeLine(stream, '');
1008
+ for (const [name, result] of Object.entries(validation)) {
1009
+ const normalized = String(result);
1010
+ const ok = normalized === 'ok' || normalized === 'true';
1011
+ writeLine(stream, ` ${ok ? '✓' : '✗'} ${name.replace(/_/g, ' ')}: ${normalized}`);
1012
+ }
1013
+ writeLine(stream, '');
1014
+ };
1015
+ export const runSetupCommand = async (params, options = {}) => {
1016
+ const mode = validateSetupMode(params.mode);
1017
+ const yes = Boolean(params.yes);
1018
+ const api_key = params.api_key?.trim() || null;
1019
+ const runDoctor = options.run_doctor_checks ??
1020
+ ((doctorOptions) => runDoctorChecks(doctorOptions));
1021
+ const installCommand = options.install_command ?? runInstallCommand;
1022
+ const saveApiKey = options.save_api_key ?? save_cloud_api_token;
1023
+ const output = options.stdout ?? process.stdout;
1024
+ const json_output = Boolean(options.json_output);
1025
+ const initialReport = await runDoctor();
1026
+ const checks = renderSetupChecks(mode, initialReport);
1027
+ const actions = planSetupActions(mode, checks, yes, api_key);
1028
+ if (!json_output) {
1029
+ logSetupChecks(output, checks);
1030
+ logSetupActions(output, actions);
1031
+ }
1032
+ for (const action of actions) {
1033
+ if (action.type === 'install_browser') {
1034
+ if (!json_output) {
1035
+ writeLine(output, 'Installing Chromium browser...');
1036
+ }
1037
+ await installCommand();
1038
+ continue;
1039
+ }
1040
+ if (action.type === 'configure_api_key') {
1041
+ if (!json_output) {
1042
+ writeLine(output, 'Configuring API key...');
1043
+ }
1044
+ saveApiKey(action.api_key);
1045
+ continue;
1046
+ }
1047
+ if (action.type === 'prompt_api_key' && !json_output) {
1048
+ writeLine(output, 'API key not configured');
1049
+ writeLine(output, ' Set via: export BROWSER_USE_API_KEY=your_key');
1050
+ writeLine(output, ' Or: browser-use setup --api-key <key>');
1051
+ continue;
1052
+ }
1053
+ if (action.type === 'install_cloudflared' && !json_output) {
1054
+ writeLine(output, 'cloudflared not installed');
1055
+ writeLine(output, ' Install via:');
1056
+ writeLine(output, ' macOS: brew install cloudflared');
1057
+ writeLine(output, ' Linux: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o ~/.local/bin/cloudflared && chmod +x ~/.local/bin/cloudflared');
1058
+ writeLine(output, ' Windows: winget install Cloudflare.cloudflared');
1059
+ writeLine(output, '');
1060
+ }
1061
+ }
1062
+ const validationReport = await runDoctor();
1063
+ const validation = {
1064
+ browser_use_import: 'ok',
1065
+ };
1066
+ if (mode === 'local' || mode === 'full') {
1067
+ validation.browser_available =
1068
+ validationReport.checks.browser.status === 'ok'
1069
+ ? 'ok'
1070
+ : `failed: ${validationReport.checks.browser.message}`;
1071
+ }
1072
+ if (mode === 'remote' || mode === 'full') {
1073
+ validation.api_key_available =
1074
+ validationReport.checks.api_key.status === 'ok';
1075
+ validation.cloudflared_available =
1076
+ validationReport.checks.cloudflared.status === 'ok';
1077
+ }
1078
+ const result = {
1079
+ status: 'success',
1080
+ mode,
1081
+ checks,
1082
+ validation,
1083
+ };
1084
+ if (json_output) {
1085
+ writeLine(output, JSON.stringify(result, null, 2));
1086
+ }
1087
+ else {
1088
+ logSetupValidation(output, validation);
1089
+ }
1090
+ return 0;
1091
+ };
1092
+ const formatDuration = (startedAt, finishedAt) => {
1093
+ if (!startedAt) {
1094
+ return '';
1095
+ }
1096
+ const start = new Date(startedAt).getTime();
1097
+ const end = finishedAt ? new Date(finishedAt).getTime() : Date.now();
1098
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) {
1099
+ return '';
1100
+ }
1101
+ const totalSeconds = Math.floor((end - start) / 1000);
1102
+ if (totalSeconds < 60) {
1103
+ return `${totalSeconds}s`;
1104
+ }
1105
+ if (totalSeconds < 3600) {
1106
+ return `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s`;
1107
+ }
1108
+ return `${Math.floor(totalSeconds / 3600)}h ${Math.floor((totalSeconds % 3600) / 60)}m`;
1109
+ };
1110
+ const printTaskStep = (stream, step, verbose) => {
1111
+ const stepNumber = step.number ?? '?';
1112
+ const memory = String(step.memory ?? '');
1113
+ if (verbose) {
1114
+ const url = String(step.url ?? '');
1115
+ const shortUrl = url.length > 60 ? `${url.slice(0, 57)}...` : url;
1116
+ writeLine(stream, ` [${stepNumber}] ${shortUrl}`);
1117
+ if (memory) {
1118
+ const shortMemory = memory.length > 100 ? `${memory.slice(0, 97)}...` : memory;
1119
+ writeLine(stream, ` Reasoning: ${shortMemory}`);
1120
+ }
1121
+ const actions = Array.isArray(step.actions) ? step.actions : [];
1122
+ actions.slice(0, 2).forEach((action) => {
1123
+ const text = String(action);
1124
+ const shortAction = text.length > 70 ? `${text.slice(0, 67)}...` : text;
1125
+ writeLine(stream, ` Action: ${shortAction}`);
1126
+ });
1127
+ if (actions.length > 2) {
1128
+ writeLine(stream, ` ... and ${actions.length - 2} more actions`);
1129
+ }
1130
+ return;
1131
+ }
1132
+ const shortMemory = memory
1133
+ ? memory.length > 80
1134
+ ? `${memory.slice(0, 77)}...`
1135
+ : memory
1136
+ : '(no reasoning)';
1137
+ writeLine(stream, ` ${stepNumber}. ${shortMemory}`);
1138
+ };
1139
+ const requireCommandTarget = (value, usage) => {
1140
+ const target = value?.trim();
1141
+ if (!target || target.startsWith('-')) {
1142
+ throw new Error(usage);
1143
+ }
1144
+ return target;
1145
+ };
1146
+ const rejectUnexpectedPositionals = (positionals, usage) => {
1147
+ if (positionals.length > 0) {
1148
+ throw new Error(usage);
1149
+ }
1150
+ };
1151
+ const markUsedOption = (used_options, option) => {
1152
+ if (!used_options.includes(option)) {
1153
+ used_options.push(option);
1154
+ }
1155
+ };
1156
+ const rejectUnsupportedFlags = (used_options, allowed_options, usage) => {
1157
+ const allowed = new Set(allowed_options);
1158
+ const unsupported = used_options.find((option) => !allowed.has(option));
1159
+ if (unsupported) {
1160
+ throw new Error(usage);
1161
+ }
1162
+ };
1163
+ const parseTaskCommandFlags = (argv) => {
1164
+ const flags = {
1165
+ json: false,
1166
+ limit: 10,
1167
+ status: null,
1168
+ session: null,
1169
+ compact: false,
1170
+ verbose: false,
1171
+ last: null,
1172
+ reverse: false,
1173
+ step: null,
1174
+ positionals: [],
1175
+ used_options: [],
1176
+ };
1177
+ for (let index = 0; index < argv.length; index += 1) {
1178
+ const arg = argv[index] ?? '';
1179
+ if (arg === '--json') {
1180
+ flags.json = true;
1181
+ markUsedOption(flags.used_options, '--json');
1182
+ continue;
1183
+ }
1184
+ if (arg === '--compact' || arg === '-c') {
1185
+ flags.compact = true;
1186
+ markUsedOption(flags.used_options, '--compact');
1187
+ continue;
1188
+ }
1189
+ if (arg === '--verbose' || arg === '-v') {
1190
+ flags.verbose = true;
1191
+ markUsedOption(flags.used_options, '--verbose');
1192
+ continue;
1193
+ }
1194
+ if (arg === '--reverse' || arg === '-r') {
1195
+ flags.reverse = true;
1196
+ markUsedOption(flags.used_options, '--reverse');
1197
+ continue;
1198
+ }
1199
+ if (arg === '--limit' || arg.startsWith('--limit=')) {
1200
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1201
+ flags.limit = parsePositiveInt('--limit', value);
1202
+ markUsedOption(flags.used_options, '--limit');
1203
+ index = nextIndex;
1204
+ continue;
1205
+ }
1206
+ if (arg === '--status' || arg.startsWith('--status=')) {
1207
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1208
+ flags.status = value;
1209
+ markUsedOption(flags.used_options, '--status');
1210
+ index = nextIndex;
1211
+ continue;
1212
+ }
1213
+ if (arg === '--session' || arg.startsWith('--session=')) {
1214
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1215
+ flags.session = value;
1216
+ markUsedOption(flags.used_options, '--session');
1217
+ index = nextIndex;
1218
+ continue;
1219
+ }
1220
+ if (arg === '--last' || arg === '-n' || arg.startsWith('--last=')) {
1221
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1222
+ flags.last = parsePositiveInt('--last', value);
1223
+ markUsedOption(flags.used_options, '--last');
1224
+ index = nextIndex;
1225
+ continue;
1226
+ }
1227
+ if (arg === '--step' || arg === '-s' || arg.startsWith('--step=')) {
1228
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1229
+ flags.step = parsePositiveInt('--step', value);
1230
+ markUsedOption(flags.used_options, '--step');
1231
+ index = nextIndex;
1232
+ continue;
1233
+ }
1234
+ if (arg.startsWith('-')) {
1235
+ throw new Error(`Unknown option: ${arg}`);
1236
+ }
1237
+ flags.positionals.push(arg);
1238
+ }
1239
+ return flags;
1240
+ };
1241
+ export const runTaskCommand = async (argv, options = {}) => {
1242
+ const client = options.client ?? new CloudManagementClient();
1243
+ const output = options.stdout ?? process.stdout;
1244
+ const errorOutput = options.stderr ?? process.stderr;
1245
+ const subcommand = argv[0] ?? '';
1246
+ try {
1247
+ if (subcommand === 'list') {
1248
+ const flags = parseTaskCommandFlags(argv.slice(1));
1249
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use task list [options]');
1250
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--limit', '--status', '--session'], 'Usage: browser-use task list [options]');
1251
+ const result = await client.list_tasks({
1252
+ pageSize: flags.limit,
1253
+ filterBy: flags.status,
1254
+ sessionId: flags.session,
1255
+ });
1256
+ if (flags.json) {
1257
+ writeLine(output, JSON.stringify(result.items, null, 2));
1258
+ return 0;
1259
+ }
1260
+ if (result.items.length === 0) {
1261
+ writeLine(output, 'No tasks found');
1262
+ return 0;
1263
+ }
1264
+ writeLine(output, `Tasks (${result.items.length}):`);
1265
+ for (const task of result.items) {
1266
+ const emoji = {
1267
+ created: '🕒',
1268
+ started: '🔄',
1269
+ running: '🔄',
1270
+ finished: '✅',
1271
+ stopped: '⏹️',
1272
+ failed: '❌',
1273
+ }[task.status] ?? '❓';
1274
+ const text = task.task.length > 50 ? `${task.task.slice(0, 47)}...` : task.task;
1275
+ writeLine(output, ` ${emoji} ${task.id.slice(0, 8)}... [${task.status}] ${text}`);
1276
+ }
1277
+ return 0;
1278
+ }
1279
+ if (subcommand === 'status') {
1280
+ const taskId = requireCommandTarget(argv[1], 'Usage: browser-use task status <task-id>');
1281
+ const flags = parseTaskCommandFlags(argv.slice(2));
1282
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use task status <task-id>');
1283
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--compact', '--verbose', '--reverse', '--last', '--step'], 'Usage: browser-use task status <task-id>');
1284
+ const task = await client.get_task(taskId);
1285
+ if (flags.json) {
1286
+ writeLine(output, JSON.stringify(task, null, 2));
1287
+ return 0;
1288
+ }
1289
+ const parts = [
1290
+ `${{
1291
+ created: '🕒',
1292
+ started: '🔄',
1293
+ running: '🔄',
1294
+ finished: '✅',
1295
+ stopped: '⏹️',
1296
+ failed: '❌',
1297
+ }[task.status] ?? '❓'} ${task.id.slice(0, 8)}... [${task.status}]`,
1298
+ ];
1299
+ const duration = formatDuration(task.startedAt, task.finishedAt);
1300
+ if (duration) {
1301
+ parts.push(duration);
1302
+ }
1303
+ writeLine(output, parts.join(' '));
1304
+ let steps = [...(task.steps ?? [])];
1305
+ const showAllSteps = flags.compact || flags.verbose;
1306
+ if (flags.step !== null) {
1307
+ steps = steps.filter((step) => Number(step.number) === flags.step);
1308
+ }
1309
+ else if (!showAllSteps && steps.length > 1) {
1310
+ writeLine(output, ` ... ${steps.length - 1} earlier steps`);
1311
+ steps = [steps[steps.length - 1]];
1312
+ }
1313
+ else if (flags.last !== null && flags.last < steps.length) {
1314
+ writeLine(output, ` ... ${steps.length - flags.last} earlier steps`);
1315
+ steps = steps.slice(-flags.last);
1316
+ }
1317
+ if (flags.reverse) {
1318
+ steps.reverse();
1319
+ }
1320
+ steps.forEach((step) => printTaskStep(output, step, flags.verbose));
1321
+ if (task.output) {
1322
+ writeLine(output, '');
1323
+ writeLine(output, `Output: ${task.output}`);
1324
+ }
1325
+ return 0;
1326
+ }
1327
+ if (subcommand === 'stop') {
1328
+ const taskId = requireCommandTarget(argv[1], 'Usage: browser-use task stop <task-id>');
1329
+ const flags = parseTaskCommandFlags(argv.slice(2));
1330
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use task stop <task-id>');
1331
+ rejectUnsupportedFlags(flags.used_options, ['--json'], 'Usage: browser-use task stop <task-id>');
1332
+ await client.update_task(taskId, 'stop');
1333
+ if (flags.json) {
1334
+ writeLine(output, JSON.stringify({ stopped: taskId }, null, 2));
1335
+ }
1336
+ else {
1337
+ writeLine(output, `Stopped task: ${taskId}`);
1338
+ }
1339
+ return 0;
1340
+ }
1341
+ if (subcommand === 'logs') {
1342
+ const taskId = requireCommandTarget(argv[1], 'Usage: browser-use task logs <task-id>');
1343
+ const flags = parseTaskCommandFlags(argv.slice(2));
1344
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use task logs <task-id>');
1345
+ rejectUnsupportedFlags(flags.used_options, ['--json'], 'Usage: browser-use task logs <task-id>');
1346
+ const result = await client.get_task_logs(taskId);
1347
+ if (flags.json) {
1348
+ writeLine(output, JSON.stringify(result, null, 2));
1349
+ }
1350
+ else if (result.downloadUrl) {
1351
+ writeLine(output, `Download logs: ${result.downloadUrl}`);
1352
+ }
1353
+ else {
1354
+ writeLine(output, 'No logs available for this task');
1355
+ }
1356
+ return 0;
1357
+ }
1358
+ writeLine(errorOutput, 'Usage: browser-use task <list|status|stop|logs> [options]');
1359
+ return 1;
1360
+ }
1361
+ catch (error) {
1362
+ writeLine(errorOutput, `Error: ${error.message}`);
1363
+ return 1;
1364
+ }
1365
+ };
1366
+ const parseSessionCommandFlags = (argv) => {
1367
+ const flags = {
1368
+ json: false,
1369
+ limit: 10,
1370
+ status: null,
1371
+ all: false,
1372
+ delete: false,
1373
+ profile: null,
1374
+ proxy_country: null,
1375
+ start_url: null,
1376
+ screen_size: null,
1377
+ positionals: [],
1378
+ used_options: [],
1379
+ };
1380
+ for (let index = 0; index < argv.length; index += 1) {
1381
+ const arg = argv[index] ?? '';
1382
+ if (arg === '--json') {
1383
+ flags.json = true;
1384
+ markUsedOption(flags.used_options, '--json');
1385
+ continue;
1386
+ }
1387
+ if (arg === '--all') {
1388
+ flags.all = true;
1389
+ markUsedOption(flags.used_options, '--all');
1390
+ continue;
1391
+ }
1392
+ if (arg === '--delete') {
1393
+ flags.delete = true;
1394
+ markUsedOption(flags.used_options, '--delete');
1395
+ continue;
1396
+ }
1397
+ if (arg === '--limit' || arg.startsWith('--limit=')) {
1398
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1399
+ flags.limit = parsePositiveInt('--limit', value);
1400
+ markUsedOption(flags.used_options, '--limit');
1401
+ index = nextIndex;
1402
+ continue;
1403
+ }
1404
+ if (arg === '--status' || arg.startsWith('--status=')) {
1405
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1406
+ flags.status = value;
1407
+ markUsedOption(flags.used_options, '--status');
1408
+ index = nextIndex;
1409
+ continue;
1410
+ }
1411
+ if (arg === '--profile' || arg.startsWith('--profile=')) {
1412
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1413
+ flags.profile = value;
1414
+ markUsedOption(flags.used_options, '--profile');
1415
+ index = nextIndex;
1416
+ continue;
1417
+ }
1418
+ if (arg === '--proxy-country' || arg.startsWith('--proxy-country=')) {
1419
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1420
+ flags.proxy_country = value;
1421
+ markUsedOption(flags.used_options, '--proxy-country');
1422
+ index = nextIndex;
1423
+ continue;
1424
+ }
1425
+ if (arg === '--start-url' || arg.startsWith('--start-url=')) {
1426
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1427
+ flags.start_url = value;
1428
+ markUsedOption(flags.used_options, '--start-url');
1429
+ index = nextIndex;
1430
+ continue;
1431
+ }
1432
+ if (arg === '--screen-size' || arg.startsWith('--screen-size=')) {
1433
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1434
+ flags.screen_size = value;
1435
+ markUsedOption(flags.used_options, '--screen-size');
1436
+ index = nextIndex;
1437
+ continue;
1438
+ }
1439
+ if (arg.startsWith('-')) {
1440
+ throw new Error(`Unknown option: ${arg}`);
1441
+ }
1442
+ flags.positionals.push(arg);
1443
+ }
1444
+ return flags;
1445
+ };
1446
+ export const runSessionCommand = async (argv, options = {}) => {
1447
+ const client = options.client ?? new CloudManagementClient();
1448
+ const output = options.stdout ?? process.stdout;
1449
+ const errorOutput = options.stderr ?? process.stderr;
1450
+ const subcommand = argv[0] ?? '';
1451
+ try {
1452
+ if (subcommand === 'list') {
1453
+ const flags = parseSessionCommandFlags(argv.slice(1));
1454
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use session list [options]');
1455
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--limit', '--status'], 'Usage: browser-use session list [options]');
1456
+ const result = await client.list_sessions({
1457
+ pageSize: flags.limit,
1458
+ filterBy: flags.status,
1459
+ });
1460
+ if (flags.json) {
1461
+ writeLine(output, JSON.stringify(result.items, null, 2));
1462
+ return 0;
1463
+ }
1464
+ if (result.items.length === 0) {
1465
+ writeLine(output, 'No sessions found');
1466
+ return 0;
1467
+ }
1468
+ writeLine(output, `Sessions (${result.items.length}):`);
1469
+ for (const session of result.items) {
1470
+ const emoji = session.status === 'active' ? '🟢' : '⏹️';
1471
+ const duration = formatDuration(session.startedAt, session.finishedAt);
1472
+ const details = duration ? ` ${duration}` : '';
1473
+ writeLine(output, ` ${emoji} ${session.id.slice(0, 8)}... [${session.status}]${details}`);
1474
+ }
1475
+ return 0;
1476
+ }
1477
+ if (subcommand === 'get') {
1478
+ const sessionId = requireCommandTarget(argv[1], 'Usage: browser-use session get <session-id>');
1479
+ const flags = parseSessionCommandFlags(argv.slice(2));
1480
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use session get <session-id>');
1481
+ rejectUnsupportedFlags(flags.used_options, ['--json'], 'Usage: browser-use session get <session-id>');
1482
+ const session = await client.get_session(sessionId);
1483
+ if (flags.json) {
1484
+ writeLine(output, JSON.stringify(session, null, 2));
1485
+ }
1486
+ else {
1487
+ writeLine(output, `${session.id} [${session.status}]`);
1488
+ if (session.liveUrl) {
1489
+ writeLine(output, `Live URL: ${session.liveUrl}`);
1490
+ }
1491
+ const duration = formatDuration(session.startedAt, session.finishedAt);
1492
+ if (duration) {
1493
+ writeLine(output, `Duration: ${duration}`);
1494
+ }
1495
+ }
1496
+ return 0;
1497
+ }
1498
+ if (subcommand === 'stop') {
1499
+ const flags = parseSessionCommandFlags(argv.slice(1));
1500
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--all'], 'Usage: browser-use session stop <session-id> | --all');
1501
+ if (flags.all) {
1502
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use session stop <session-id> | --all');
1503
+ const sessions = await client.list_sessions({
1504
+ pageSize: 100,
1505
+ filterBy: 'active',
1506
+ });
1507
+ const stopped = [];
1508
+ for (const session of sessions.items) {
1509
+ await client.update_session(session.id, 'stop');
1510
+ stopped.push(session.id);
1511
+ }
1512
+ if (flags.json) {
1513
+ writeLine(output, JSON.stringify({ stopped }, null, 2));
1514
+ }
1515
+ else if (stopped.length > 0) {
1516
+ writeLine(output, `Stopped ${stopped.length} session(s): ${stopped.join(', ')}`);
1517
+ }
1518
+ else {
1519
+ writeLine(output, 'No sessions to stop');
1520
+ }
1521
+ return 0;
1522
+ }
1523
+ const [sessionIdCandidate, ...unexpectedPositionals] = flags.positionals;
1524
+ rejectUnexpectedPositionals(unexpectedPositionals, 'Usage: browser-use session stop <session-id> | --all');
1525
+ const sessionId = requireCommandTarget(sessionIdCandidate ?? argv[1], 'Usage: browser-use session stop <session-id> | --all');
1526
+ await client.update_session(sessionId, 'stop');
1527
+ if (flags.json) {
1528
+ writeLine(output, JSON.stringify({ stopped: sessionId }, null, 2));
1529
+ }
1530
+ else {
1531
+ writeLine(output, `Stopped session: ${sessionId}`);
1532
+ }
1533
+ return 0;
1534
+ }
1535
+ if (subcommand === 'create') {
1536
+ const flags = parseSessionCommandFlags(argv.slice(1));
1537
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use session create [options]');
1538
+ rejectUnsupportedFlags(flags.used_options, [
1539
+ '--json',
1540
+ '--profile',
1541
+ '--proxy-country',
1542
+ '--start-url',
1543
+ '--screen-size',
1544
+ ], 'Usage: browser-use session create [options]');
1545
+ let browserScreenWidth = null;
1546
+ let browserScreenHeight = null;
1547
+ if (flags.screen_size) {
1548
+ const match = flags.screen_size.match(/^(\d+)x(\d+)$/i);
1549
+ if (!match) {
1550
+ throw new Error('Expected --screen-size WIDTHxHEIGHT');
1551
+ }
1552
+ browserScreenWidth = Number.parseInt(match[1], 10);
1553
+ browserScreenHeight = Number.parseInt(match[2], 10);
1554
+ }
1555
+ const session = await client.create_session({
1556
+ profileId: flags.profile,
1557
+ proxyCountryCode: flags.proxy_country,
1558
+ startUrl: flags.start_url,
1559
+ browserScreenWidth,
1560
+ browserScreenHeight,
1561
+ });
1562
+ if (flags.json) {
1563
+ writeLine(output, JSON.stringify(session, null, 2));
1564
+ }
1565
+ else {
1566
+ writeLine(output, `Created session: ${session.id}`);
1567
+ if (session.liveUrl) {
1568
+ writeLine(output, `Live URL: ${session.liveUrl}`);
1569
+ }
1570
+ }
1571
+ return 0;
1572
+ }
1573
+ if (subcommand === 'share') {
1574
+ const sessionId = requireCommandTarget(argv[1], 'Usage: browser-use session share <session-id> [--delete]');
1575
+ const flags = parseSessionCommandFlags(argv.slice(2));
1576
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use session share <session-id> [--delete]');
1577
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--delete'], 'Usage: browser-use session share <session-id> [--delete]');
1578
+ if (flags.delete) {
1579
+ await client.delete_session_public_share(sessionId);
1580
+ if (flags.json) {
1581
+ writeLine(output, JSON.stringify({ deleted: sessionId }, null, 2));
1582
+ }
1583
+ else {
1584
+ writeLine(output, `Deleted public share for session: ${sessionId}`);
1585
+ }
1586
+ }
1587
+ else {
1588
+ const share = await client.create_session_public_share(sessionId);
1589
+ if (flags.json) {
1590
+ writeLine(output, JSON.stringify(share, null, 2));
1591
+ }
1592
+ else {
1593
+ writeLine(output, `Public share URL: ${share.shareUrl}`);
1594
+ }
1595
+ }
1596
+ return 0;
1597
+ }
1598
+ writeLine(errorOutput, 'Usage: browser-use session <list|get|stop|create|share> [options]');
1599
+ return 1;
1600
+ }
1601
+ catch (error) {
1602
+ writeLine(errorOutput, `Error: ${error.message}`);
1603
+ return 1;
1604
+ }
1605
+ };
1606
+ const parseProfileCommandFlags = (argv) => {
1607
+ const flags = {
1608
+ json: false,
1609
+ remote: false,
1610
+ limit: 20,
1611
+ name: null,
1612
+ domain: null,
1613
+ from_profile: null,
1614
+ positionals: [],
1615
+ used_options: [],
1616
+ };
1617
+ for (let index = 0; index < argv.length; index += 1) {
1618
+ const arg = argv[index] ?? '';
1619
+ if (arg === '--json') {
1620
+ flags.json = true;
1621
+ markUsedOption(flags.used_options, '--json');
1622
+ continue;
1623
+ }
1624
+ if (arg === '--remote') {
1625
+ flags.remote = true;
1626
+ markUsedOption(flags.used_options, '--remote');
1627
+ continue;
1628
+ }
1629
+ if (arg === '--limit' || arg.startsWith('--limit=')) {
1630
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1631
+ flags.limit = parsePositiveInt('--limit', value);
1632
+ markUsedOption(flags.used_options, '--limit');
1633
+ index = nextIndex;
1634
+ continue;
1635
+ }
1636
+ if (arg === '--name' || arg.startsWith('--name=')) {
1637
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1638
+ flags.name = value;
1639
+ markUsedOption(flags.used_options, '--name');
1640
+ index = nextIndex;
1641
+ continue;
1642
+ }
1643
+ if (arg === '--domain' || arg.startsWith('--domain=')) {
1644
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1645
+ flags.domain = value;
1646
+ markUsedOption(flags.used_options, '--domain');
1647
+ index = nextIndex;
1648
+ continue;
1649
+ }
1650
+ if (arg === '--from' || arg.startsWith('--from=')) {
1651
+ const { value, nextIndex } = takeOptionValue(arg, index, argv);
1652
+ flags.from_profile = value;
1653
+ markUsedOption(flags.used_options, '--from');
1654
+ index = nextIndex;
1655
+ continue;
1656
+ }
1657
+ if (arg.startsWith('-')) {
1658
+ throw new Error(`Unknown option: ${arg}`);
1659
+ }
1660
+ flags.positionals.push(arg);
1661
+ }
1662
+ return flags;
1663
+ };
1664
+ const normalizeProfileCookieDomain = (value) => String(value ?? '')
1665
+ .trim()
1666
+ .replace(/^\./, '')
1667
+ .toLowerCase();
1668
+ const cookieMatchesDomainFilter = (cookie, domainFilter) => {
1669
+ if (!domainFilter) {
1670
+ return true;
1671
+ }
1672
+ const cookieDomain = normalizeProfileCookieDomain(cookie.domain);
1673
+ const normalizedFilter = normalizeProfileCookieDomain(domainFilter);
1674
+ return Boolean(cookieDomain &&
1675
+ normalizedFilter &&
1676
+ (cookieDomain === normalizedFilter ||
1677
+ cookieDomain.endsWith(`.${normalizedFilter}`) ||
1678
+ normalizedFilter.endsWith(`.${cookieDomain}`)));
1679
+ };
1680
+ export const runProfileCommand = async (argv, options = {}) => {
1681
+ const client = options.client ?? new CloudManagementClient();
1682
+ const profileLister = options.profile_lister ?? (() => systemChrome.listProfiles());
1683
+ const localSessionFactory = options.local_session_factory ??
1684
+ ((profile_directory) => BrowserSession.from_system_chrome({
1685
+ profile_directory,
1686
+ profile: { headless: true },
1687
+ }));
1688
+ const remoteSessionFactory = options.remote_session_factory ??
1689
+ ((init) => new BrowserSession({
1690
+ cdp_url: init.cdp_url,
1691
+ }));
1692
+ const cloudBrowserClientFactory = options.cloud_browser_client_factory ?? (() => new CloudBrowserClient());
1693
+ const output = options.stdout ?? process.stdout;
1694
+ const errorOutput = options.stderr ?? process.stderr;
1695
+ const subcommand = argv[0] ?? '';
1696
+ try {
1697
+ if (subcommand === 'list') {
1698
+ const flags = parseProfileCommandFlags(argv.slice(1));
1699
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile list [--remote] [options]');
1700
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--remote', '--limit'], 'Usage: browser-use profile list [--remote] [options]');
1701
+ if (flags.remote) {
1702
+ const result = await client.list_profiles({
1703
+ pageSize: flags.limit,
1704
+ });
1705
+ if (flags.json) {
1706
+ writeLine(output, JSON.stringify(result.items, null, 2));
1707
+ }
1708
+ else if (result.items.length === 0) {
1709
+ writeLine(output, 'No cloud profiles found');
1710
+ }
1711
+ else {
1712
+ writeLine(output, `Cloud profiles (${result.items.length}):`);
1713
+ result.items.forEach((profile) => {
1714
+ writeLine(output, ` ${profile.id}: ${profile.name || 'Unnamed'}`);
1715
+ });
1716
+ }
1717
+ return 0;
1718
+ }
1719
+ const profiles = profileLister();
1720
+ if (flags.json) {
1721
+ writeLine(output, JSON.stringify({ profiles }, null, 2));
1722
+ }
1723
+ else if (profiles.length === 0) {
1724
+ writeLine(output, 'No Chrome profiles found');
1725
+ }
1726
+ else {
1727
+ writeLine(output, 'Local Chrome profiles:');
1728
+ profiles.forEach((profile) => {
1729
+ const emailSuffix = profile.email ? ` (${profile.email})` : '';
1730
+ writeLine(output, ` ${profile.directory}: ${profile.name}${emailSuffix}`);
1731
+ });
1732
+ }
1733
+ return 0;
1734
+ }
1735
+ if (subcommand === 'get') {
1736
+ const profileId = requireCommandTarget(argv[1], 'Usage: browser-use profile get <profile-id> [--remote]');
1737
+ const flags = parseProfileCommandFlags(argv.slice(2));
1738
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile get <profile-id> [--remote]');
1739
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--remote'], 'Usage: browser-use profile get <profile-id> [--remote]');
1740
+ if (flags.remote) {
1741
+ const profile = await client.get_profile(profileId);
1742
+ if (flags.json) {
1743
+ writeLine(output, JSON.stringify(profile, null, 2));
1744
+ }
1745
+ else {
1746
+ writeLine(output, `Profile: ${profile.id}`);
1747
+ if (profile.name) {
1748
+ writeLine(output, ` Name: ${profile.name}`);
1749
+ }
1750
+ writeLine(output, ` Updated: ${profile.updatedAt}`);
1751
+ }
1752
+ return 0;
1753
+ }
1754
+ const profile = profileLister().find((entry) => entry.directory === profileId || entry.name === profileId);
1755
+ if (!profile) {
1756
+ throw new Error(`Profile "${profileId}" not found`);
1757
+ }
1758
+ if (flags.json) {
1759
+ writeLine(output, JSON.stringify(profile, null, 2));
1760
+ }
1761
+ else {
1762
+ writeLine(output, `Profile: ${profile.directory}`);
1763
+ writeLine(output, ` Name: ${profile.name}`);
1764
+ if (profile.email) {
1765
+ writeLine(output, ` Email: ${profile.email}`);
1766
+ }
1767
+ }
1768
+ return 0;
1769
+ }
1770
+ if (subcommand === 'create') {
1771
+ const flags = parseProfileCommandFlags(argv.slice(1));
1772
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile create --remote [--name <name>] [--json]');
1773
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--remote', '--name'], 'Usage: browser-use profile create --remote [--name <name>] [--json]');
1774
+ if (!flags.remote) {
1775
+ throw new Error('Profile create is only supported with --remote');
1776
+ }
1777
+ const profile = await client.create_profile({ name: flags.name });
1778
+ if (flags.json) {
1779
+ writeLine(output, JSON.stringify(profile, null, 2));
1780
+ }
1781
+ else {
1782
+ writeLine(output, `Created cloud profile: ${profile.id}`);
1783
+ }
1784
+ return 0;
1785
+ }
1786
+ if (subcommand === 'delete') {
1787
+ const profileId = requireCommandTarget(argv[1], 'Usage: browser-use profile delete <profile-id> --remote');
1788
+ const flags = parseProfileCommandFlags(argv.slice(2));
1789
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile delete <profile-id> --remote');
1790
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--remote'], 'Usage: browser-use profile delete <profile-id> --remote');
1791
+ if (!flags.remote) {
1792
+ throw new Error('Profile delete is only supported with --remote');
1793
+ }
1794
+ await client.delete_profile(profileId);
1795
+ if (flags.json) {
1796
+ writeLine(output, JSON.stringify({ deleted: profileId }, null, 2));
1797
+ }
1798
+ else {
1799
+ writeLine(output, `Deleted cloud profile: ${profileId}`);
1800
+ }
1801
+ return 0;
1802
+ }
1803
+ if (subcommand === 'cookies') {
1804
+ const profileId = requireCommandTarget(argv[1], 'Usage: browser-use profile cookies <profile-id>');
1805
+ const flags = parseProfileCommandFlags(argv.slice(2));
1806
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile cookies <profile-id>');
1807
+ rejectUnsupportedFlags(flags.used_options, ['--json'], 'Usage: browser-use profile cookies <profile-id>');
1808
+ if (flags.remote) {
1809
+ throw new Error('Profile cookies is only supported for local Chrome profiles');
1810
+ }
1811
+ const profile = profileLister().find((entry) => entry.directory === profileId || entry.name === profileId);
1812
+ if (!profile) {
1813
+ throw new Error(`Profile "${profileId}" not found`);
1814
+ }
1815
+ const session = localSessionFactory(profile.directory);
1816
+ await session.start();
1817
+ try {
1818
+ const cookies = (await session.get_cookies?.()) ?? [];
1819
+ const domains = new Map();
1820
+ for (const cookie of cookies) {
1821
+ const domain = normalizeProfileCookieDomain(cookie.domain) || 'unknown';
1822
+ domains.set(domain, (domains.get(domain) ?? 0) + 1);
1823
+ }
1824
+ const sortedDomains = Array.from(domains.entries()).sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
1825
+ if (flags.json) {
1826
+ writeLine(output, JSON.stringify({
1827
+ domains: Object.fromEntries(sortedDomains),
1828
+ total_cookies: cookies.length,
1829
+ }, null, 2));
1830
+ }
1831
+ else {
1832
+ const emailSuffix = profile.email ? ` (${profile.email})` : '';
1833
+ writeLine(output, `Loading cookies from: ${profile.name}${emailSuffix}`);
1834
+ writeLine(output, '');
1835
+ writeLine(output, `Cookies by domain (${cookies.length} total):`);
1836
+ sortedDomains.slice(0, 20).forEach(([domain, count]) => {
1837
+ writeLine(output, ` ${domain}: ${count}`);
1838
+ });
1839
+ if (sortedDomains.length > 20) {
1840
+ writeLine(output, ` ... and ${sortedDomains.length - 20} more domains`);
1841
+ }
1842
+ writeLine(output, '');
1843
+ writeLine(output, 'To sync cookies to cloud:');
1844
+ writeLine(output, ` browser-use profile sync --from "${profile.directory}" --domain <domain>`);
1845
+ }
1846
+ }
1847
+ finally {
1848
+ await session.stop?.();
1849
+ }
1850
+ return 0;
1851
+ }
1852
+ if (subcommand === 'sync') {
1853
+ const flags = parseProfileCommandFlags(argv.slice(1));
1854
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile sync --from <profile-id> [--name <name>] [--domain <domain>] [--json]');
1855
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--from', '--name', '--domain'], 'Usage: browser-use profile sync --from <profile-id> [--name <name>] [--domain <domain>] [--json]');
1856
+ const profiles = profileLister();
1857
+ const fromProfile = flags.from_profile?.trim();
1858
+ if (!fromProfile) {
1859
+ writeLine(errorOutput, 'Usage: browser-use profile sync --from <profile-id> [--name <name>] [--domain <domain>] [--json]');
1860
+ if (profiles.length > 0) {
1861
+ writeLine(errorOutput, 'Available local profiles:');
1862
+ profiles.forEach((profile) => {
1863
+ const emailSuffix = profile.email ? ` (${profile.email})` : '';
1864
+ writeLine(errorOutput, ` ${profile.directory}: ${profile.name}${emailSuffix}`);
1865
+ });
1866
+ }
1867
+ return 1;
1868
+ }
1869
+ const profile = profiles.find((entry) => entry.directory === fromProfile || entry.name === fromProfile);
1870
+ if (!profile) {
1871
+ throw new Error(`Profile "${fromProfile}" not found`);
1872
+ }
1873
+ const progress = flags.json ? errorOutput : output;
1874
+ const logProgress = (message) => writeLine(progress, message);
1875
+ const cloudName = flags.name ??
1876
+ (flags.domain
1877
+ ? `Chrome - ${profile.name} (${flags.domain})`
1878
+ : `Chrome - ${profile.name}`);
1879
+ let cloudProfileId = null;
1880
+ let cloudBrowserSessionId = null;
1881
+ const cloudBrowserClient = cloudBrowserClientFactory();
1882
+ try {
1883
+ logProgress(`Syncing: ${profile.name}${profile.email ? ` (${profile.email})` : ''}`);
1884
+ logProgress(' Creating cloud profile...');
1885
+ const cloudProfile = await client.create_profile({ name: cloudName });
1886
+ cloudProfileId = cloudProfile.id;
1887
+ logProgress(` Created: ${cloudProfileId}`);
1888
+ logProgress(' Exporting cookies from local profile...');
1889
+ const localSession = localSessionFactory(profile.directory);
1890
+ let filteredCookies = [];
1891
+ await localSession.start();
1892
+ try {
1893
+ const cookies = ((await localSession.get_cookies?.()) ??
1894
+ []);
1895
+ filteredCookies = cookies.filter((cookie) => cookieMatchesDomainFilter(cookie, flags.domain));
1896
+ }
1897
+ finally {
1898
+ await localSession.stop?.();
1899
+ }
1900
+ if (filteredCookies.length === 0) {
1901
+ throw new Error(flags.domain
1902
+ ? `No cookies found for domain: ${flags.domain}`
1903
+ : 'No cookies found in local profile');
1904
+ }
1905
+ logProgress(` Found ${filteredCookies.length} cookies`);
1906
+ logProgress(' Importing cookies to cloud profile...');
1907
+ const cloudBrowser = await cloudBrowserClient.create_browser({
1908
+ profile_id: cloudProfileId,
1909
+ });
1910
+ cloudBrowserSessionId = cloudBrowser.id;
1911
+ if (!cloudBrowser.cdpUrl) {
1912
+ throw new Error('Cloud browser did not return a CDP URL');
1913
+ }
1914
+ const remoteSession = remoteSessionFactory({
1915
+ cdp_url: cloudBrowser.cdpUrl,
1916
+ });
1917
+ await remoteSession.start();
1918
+ try {
1919
+ if (!remoteSession.browser_context?.addCookies) {
1920
+ throw new Error('Remote browser context does not support addCookies');
1921
+ }
1922
+ await remoteSession.browser_context.addCookies(filteredCookies);
1923
+ }
1924
+ finally {
1925
+ await remoteSession.stop?.();
1926
+ }
1927
+ await cloudBrowserClient.stop_browser(cloudBrowserSessionId);
1928
+ cloudBrowserSessionId = null;
1929
+ if (flags.json) {
1930
+ writeLine(output, JSON.stringify({
1931
+ success: true,
1932
+ profile_id: cloudProfileId,
1933
+ cookies_synced: filteredCookies.length,
1934
+ }, null, 2));
1935
+ }
1936
+ else {
1937
+ logProgress('Profile synced successfully!');
1938
+ logProgress(` Cloud profile ID: ${cloudProfileId}`);
1939
+ }
1940
+ return 0;
1941
+ }
1942
+ catch (error) {
1943
+ if (cloudBrowserSessionId) {
1944
+ try {
1945
+ await cloudBrowserClient.stop_browser(cloudBrowserSessionId);
1946
+ }
1947
+ catch {
1948
+ // Ignore cleanup failures.
1949
+ }
1950
+ }
1951
+ if (cloudProfileId) {
1952
+ try {
1953
+ await client.delete_profile(cloudProfileId);
1954
+ }
1955
+ catch {
1956
+ // Ignore cleanup failures.
1957
+ }
1958
+ }
1959
+ throw error;
1960
+ }
1961
+ }
1962
+ if (subcommand === 'update') {
1963
+ const profileId = requireCommandTarget(argv[1], 'Usage: browser-use profile update <profile-id> --remote --name <name>');
1964
+ const flags = parseProfileCommandFlags(argv.slice(2));
1965
+ rejectUnexpectedPositionals(flags.positionals, 'Usage: browser-use profile update <profile-id> --remote --name <name>');
1966
+ rejectUnsupportedFlags(flags.used_options, ['--json', '--remote', '--name'], 'Usage: browser-use profile update <profile-id> --remote --name <name>');
1967
+ if (!flags.remote) {
1968
+ throw new Error('Profile update is only supported with --remote');
1969
+ }
1970
+ if (!flags.name) {
1971
+ throw new Error('Usage: browser-use profile update <profile-id> --remote --name <name>');
1972
+ }
1973
+ const profile = await client.update_profile(profileId, {
1974
+ name: flags.name,
1975
+ });
1976
+ if (flags.json) {
1977
+ writeLine(output, JSON.stringify(profile, null, 2));
1978
+ }
1979
+ else {
1980
+ writeLine(output, `Updated cloud profile: ${profile.id}`);
1981
+ }
1982
+ return 0;
1983
+ }
1984
+ writeLine(errorOutput, 'Usage: browser-use profile <list|get|create|update|delete|cookies|sync> [--remote] [options]');
1985
+ return 1;
1986
+ }
1987
+ catch (error) {
1988
+ writeLine(errorOutput, `Error: ${error.message}`);
1989
+ return 1;
1990
+ }
1991
+ };
1992
+ const CLOUD_RUN_FLAGS = new Set([
1993
+ '--remote',
1994
+ '--llm',
1995
+ '--session-id',
1996
+ '--proxy-country',
1997
+ '--wait',
1998
+ '--stream',
1999
+ '--flash',
2000
+ '--thinking',
2001
+ '--vision',
2002
+ '--no-vision',
2003
+ '--start-url',
2004
+ '--metadata',
2005
+ '--secret',
2006
+ '--allowed-domain',
2007
+ '--skill-id',
2008
+ '--structured-output',
2009
+ '--judge',
2010
+ '--judge-ground-truth',
2011
+ '--max-steps',
2012
+ '--profile',
2013
+ ]);
2014
+ const CLOUD_RUN_VALUE_FLAGS = new Set([
2015
+ '--llm',
2016
+ '--session-id',
2017
+ '--proxy-country',
2018
+ '--start-url',
2019
+ '--structured-output',
2020
+ '--judge-ground-truth',
2021
+ '--max-steps',
2022
+ '--profile',
2023
+ '--metadata',
2024
+ '--secret',
2025
+ '--allowed-domain',
2026
+ '--skill-id',
2027
+ ]);
2028
+ export const hasCloudRunFlags = (argv) => {
2029
+ for (const arg of argv) {
2030
+ if (arg === '--') {
2031
+ break;
2032
+ }
2033
+ if (CLOUD_RUN_FLAGS.has(arg)) {
2034
+ return true;
2035
+ }
2036
+ const separator = arg.indexOf('=');
2037
+ if (separator > 0 && CLOUD_RUN_VALUE_FLAGS.has(arg.slice(0, separator))) {
2038
+ return true;
2039
+ }
2040
+ }
2041
+ return false;
2042
+ };
2043
+ const hasExplicitRemoteRunFlag = (argv) => {
2044
+ for (const arg of argv) {
2045
+ if (arg === '--') {
2046
+ break;
2047
+ }
2048
+ if (arg === '--remote') {
2049
+ return true;
2050
+ }
2051
+ }
2052
+ return false;
2053
+ };
2054
+ const PREFIXED_SUBCOMMAND_VALUE_FLAGS = [
2055
+ '--api-key',
2056
+ '--provider',
2057
+ '--model',
2058
+ '--window-width',
2059
+ '--window-height',
2060
+ '--user-data-dir',
2061
+ '--profile-directory',
2062
+ '--allowed-domains',
2063
+ '--proxy-url',
2064
+ '--no-proxy',
2065
+ '--proxy-username',
2066
+ '--proxy-password',
2067
+ '--cdp-url',
2068
+ ];
2069
+ const matchesOptionWithValue = (arg, option) => arg === option || arg.startsWith(`${option}=`);
2070
+ export const extractPrefixedSubcommand = (argv) => {
2071
+ const forwardedArgs = [];
2072
+ let debug = false;
2073
+ let index = 0;
2074
+ while (index < argv.length) {
2075
+ const arg = argv[index] ?? '';
2076
+ if (arg === '--debug') {
2077
+ debug = true;
2078
+ index += 1;
2079
+ continue;
2080
+ }
2081
+ if (arg === '--json') {
2082
+ forwardedArgs.push(arg);
2083
+ index += 1;
2084
+ continue;
2085
+ }
2086
+ if (arg === '--headless') {
2087
+ index += 1;
2088
+ continue;
2089
+ }
2090
+ const valueOption = PREFIXED_SUBCOMMAND_VALUE_FLAGS.find((option) => matchesOptionWithValue(arg, option));
2091
+ if (valueOption) {
2092
+ if (arg === valueOption) {
2093
+ if (index + 1 >= argv.length) {
2094
+ break;
2095
+ }
2096
+ index += 2;
2097
+ }
2098
+ else {
2099
+ index += 1;
2100
+ }
2101
+ continue;
2102
+ }
2103
+ break;
2104
+ }
2105
+ const command = argv[index];
2106
+ if (command !== 'run' &&
2107
+ command !== 'task' &&
2108
+ command !== 'session' &&
2109
+ command !== 'profile') {
2110
+ return null;
2111
+ }
2112
+ return {
2113
+ command,
2114
+ argv: argv.slice(index + 1),
2115
+ debug,
2116
+ forwardedArgs,
2117
+ };
2118
+ };
2119
+ const parseKeyValuePairs = (values, option) => {
2120
+ const result = {};
2121
+ values.forEach((value) => {
2122
+ const separator = value.indexOf('=');
2123
+ if (separator <= 0) {
2124
+ throw new Error(`Invalid value for ${option}: expected KEY=VALUE`);
2125
+ }
2126
+ const key = value.slice(0, separator).trim();
2127
+ if (!key) {
2128
+ throw new Error(`Invalid value for ${option}: expected KEY=VALUE`);
2129
+ }
2130
+ result[key] = value.slice(separator + 1);
2131
+ });
2132
+ return Object.keys(result).length > 0 ? result : null;
2133
+ };
2134
+ const parseCloudRunArgs = (argv) => {
2135
+ const flags = {
2136
+ remote: false,
2137
+ wait: false,
2138
+ stream: false,
2139
+ flash: false,
2140
+ thinking: false,
2141
+ vision: null,
2142
+ llm: null,
2143
+ session_id: null,
2144
+ proxy_country: null,
2145
+ start_url: null,
2146
+ structured_output: null,
2147
+ judge: false,
2148
+ judge_ground_truth: null,
2149
+ max_steps: null,
2150
+ profile: null,
2151
+ metadata: [],
2152
+ secret: [],
2153
+ allowed_domain: [],
2154
+ skill_id: [],
2155
+ task_parts: [],
2156
+ };
2157
+ for (let index = 0; index < argv.length; index += 1) {
2158
+ const rawArg = argv[index] ?? '';
2159
+ if (rawArg === '--') {
2160
+ flags.task_parts.push(...argv.slice(index + 1));
2161
+ break;
2162
+ }
2163
+ const separator = rawArg.indexOf('=');
2164
+ const hasInlineValue = separator > 0;
2165
+ const arg = hasInlineValue ? rawArg.slice(0, separator) : rawArg;
2166
+ const inlineValue = hasInlineValue
2167
+ ? rawArg.slice(separator + 1).trim()
2168
+ : '';
2169
+ if (arg === '--remote') {
2170
+ flags.remote = true;
2171
+ continue;
2172
+ }
2173
+ if (arg === '--wait') {
2174
+ flags.wait = true;
2175
+ continue;
2176
+ }
2177
+ if (arg === '--stream') {
2178
+ flags.stream = true;
2179
+ continue;
2180
+ }
2181
+ if (arg === '--flash') {
2182
+ flags.flash = true;
2183
+ continue;
2184
+ }
2185
+ if (arg === '--thinking') {
2186
+ flags.thinking = true;
2187
+ continue;
2188
+ }
2189
+ if (arg === '--vision') {
2190
+ flags.vision = true;
2191
+ continue;
2192
+ }
2193
+ if (arg === '--no-vision') {
2194
+ flags.vision = false;
2195
+ continue;
2196
+ }
2197
+ if (arg === '--judge') {
2198
+ flags.judge = true;
2199
+ continue;
2200
+ }
2201
+ if (arg === '--llm' ||
2202
+ arg === '--session-id' ||
2203
+ arg === '--proxy-country' ||
2204
+ arg === '--start-url' ||
2205
+ arg === '--structured-output' ||
2206
+ arg === '--judge-ground-truth' ||
2207
+ arg === '--max-steps' ||
2208
+ arg === '--profile' ||
2209
+ arg === '--metadata' ||
2210
+ arg === '--secret' ||
2211
+ arg === '--allowed-domain' ||
2212
+ arg === '--skill-id') {
2213
+ const { value: next, nextIndex } = takeOptionValue(rawArg, index, argv);
2214
+ if (arg === '--llm') {
2215
+ flags.llm = next;
2216
+ }
2217
+ else if (arg === '--session-id') {
2218
+ flags.session_id = next;
2219
+ }
2220
+ else if (arg === '--proxy-country') {
2221
+ flags.proxy_country = next;
2222
+ }
2223
+ else if (arg === '--start-url') {
2224
+ flags.start_url = next;
2225
+ }
2226
+ else if (arg === '--structured-output') {
2227
+ flags.structured_output = next;
2228
+ }
2229
+ else if (arg === '--judge-ground-truth') {
2230
+ flags.judge_ground_truth = next;
2231
+ }
2232
+ else if (arg === '--max-steps') {
2233
+ flags.max_steps = parsePositiveInt('--max-steps', next);
2234
+ }
2235
+ else if (arg === '--profile') {
2236
+ flags.profile = next;
2237
+ }
2238
+ else if (arg === '--metadata') {
2239
+ flags.metadata.push(next);
2240
+ }
2241
+ else if (arg === '--secret') {
2242
+ flags.secret.push(next);
2243
+ }
2244
+ else if (arg === '--allowed-domain') {
2245
+ flags.allowed_domain.push(next);
2246
+ }
2247
+ else {
2248
+ flags.skill_id.push(next);
2249
+ }
2250
+ index = nextIndex;
2251
+ continue;
2252
+ }
2253
+ if (rawArg.startsWith('-')) {
2254
+ throw new Error(`Unknown option: ${rawArg}`);
2255
+ }
2256
+ flags.task_parts.push(rawArg);
2257
+ }
2258
+ return flags;
2259
+ };
2260
+ export const runCloudTaskCommand = async (argv, options = {}) => {
2261
+ const client = options.client ?? new CloudManagementClient();
2262
+ const output = options.stdout ?? process.stdout;
2263
+ const errorOutput = options.stderr ?? process.stderr;
2264
+ const sleepImpl = options.sleep_impl ??
2265
+ ((ms) => new Promise((r) => setTimeout(r, ms)));
2266
+ let autoCreatedSessionId = null;
2267
+ let taskCreated = false;
2268
+ try {
2269
+ const flags = parseCloudRunArgs(argv);
2270
+ if (!flags.remote) {
2271
+ throw new Error('Usage: browser-use run --remote <task>');
2272
+ }
2273
+ const task = flags.task_parts.join(' ').trim();
2274
+ if (!task) {
2275
+ throw new Error('Usage: browser-use run --remote <task>');
2276
+ }
2277
+ let sessionId = flags.session_id;
2278
+ if (!sessionId && (flags.profile || flags.proxy_country)) {
2279
+ const session = await client.create_session({
2280
+ profileId: flags.profile,
2281
+ proxyCountryCode: flags.proxy_country,
2282
+ startUrl: flags.start_url,
2283
+ });
2284
+ sessionId = session.id;
2285
+ autoCreatedSessionId = session.id;
2286
+ writeLine(output, `Created cloud session: ${sessionId}`);
2287
+ }
2288
+ const created = await client.create_task({
2289
+ task,
2290
+ llm: flags.llm,
2291
+ sessionId,
2292
+ startUrl: flags.start_url,
2293
+ maxSteps: flags.max_steps,
2294
+ structuredOutput: flags.structured_output,
2295
+ metadata: parseKeyValuePairs(flags.metadata, '--metadata'),
2296
+ secrets: parseKeyValuePairs(flags.secret, '--secret'),
2297
+ allowedDomains: flags.allowed_domain.length > 0 ? flags.allowed_domain : null,
2298
+ flashMode: flags.flash,
2299
+ thinking: flags.thinking,
2300
+ vision: flags.vision,
2301
+ judge: flags.judge,
2302
+ judgeGroundTruth: flags.judge_ground_truth,
2303
+ skillIds: flags.skill_id.length > 0 ? flags.skill_id : null,
2304
+ });
2305
+ taskCreated = true;
2306
+ if (!flags.wait) {
2307
+ writeLine(output, `Task started: ${created.id} (session: ${created.sessionId})`);
2308
+ writeLine(output, `Use "browser-use task status ${created.id}" to check progress.`);
2309
+ return 0;
2310
+ }
2311
+ let lastStatus = null;
2312
+ while (true) {
2313
+ const taskView = await client.get_task(created.id);
2314
+ if (flags.stream && taskView.status !== lastStatus) {
2315
+ writeLine(output, `Status: ${taskView.status}`);
2316
+ lastStatus = taskView.status;
2317
+ }
2318
+ if (taskView.status === 'finished' ||
2319
+ taskView.status === 'stopped' ||
2320
+ taskView.status === 'failed') {
2321
+ writeLine(output, `Task ${taskView.status}: ${taskView.id}`);
2322
+ if (taskView.output) {
2323
+ writeLine(output, taskView.output);
2324
+ }
2325
+ return taskView.status === 'finished' ? 0 : 1;
2326
+ }
2327
+ await sleepImpl(1000);
2328
+ }
2329
+ }
2330
+ catch (error) {
2331
+ if (autoCreatedSessionId && !taskCreated) {
2332
+ try {
2333
+ await client.update_session(autoCreatedSessionId, 'stop');
2334
+ }
2335
+ catch {
2336
+ // Ignore cleanup failures.
2337
+ }
2338
+ }
2339
+ writeLine(errorOutput, `Error: ${error.message}`);
2340
+ return 1;
2341
+ }
2342
+ };
2343
+ const enableDebugLogging = () => {
2344
+ process.env.BROWSER_USE_LOGGING_LEVEL = 'debug';
2345
+ setupLogging({ logLevel: 'debug', forceSetup: true });
2346
+ };
2347
+ const summarizeDoctorChecks = (checks) => {
2348
+ const values = Object.values(checks);
2349
+ const total = values.length;
2350
+ const ok = values.filter((check) => check.status === 'ok').length;
2351
+ const warning = values.filter((check) => check.status === 'warning').length;
2352
+ const missing = values.filter((check) => check.status === 'missing').length;
2353
+ const error = values.filter((check) => check.status === 'error').length;
2354
+ const parts = [`${ok}/${total} checks passed`];
2355
+ if (warning > 0) {
2356
+ parts.push(`${warning} warnings`);
2357
+ }
2358
+ if (missing > 0) {
2359
+ parts.push(`${missing} missing`);
2360
+ }
2361
+ if (error > 0) {
2362
+ parts.push(`${error} errors`);
2363
+ }
2364
+ return parts.join(', ');
2365
+ };
2366
+ const findSystemBinary = (binary) => {
2367
+ const command = process.platform === 'win32' ? 'where' : 'which';
2368
+ const result = spawnSync(command, [binary], {
2369
+ encoding: 'utf8',
2370
+ stdio: ['ignore', 'pipe', 'ignore'],
2371
+ });
2372
+ if (result.status !== 0) {
2373
+ return null;
2374
+ }
2375
+ const firstLine = result.stdout
2376
+ .split(/\r?\n/)
2377
+ .map((line) => line.trim())
2378
+ .find(Boolean);
2379
+ return firstLine ?? null;
2380
+ };
2381
+ export const runDoctorChecks = async (options = {}) => {
2382
+ const fetchImpl = options.fetch_impl ?? fetch;
2383
+ const configuredApiKey = options.api_key !== undefined
2384
+ ? options.api_key?.trim() || null
2385
+ : process.env.BROWSER_USE_API_KEY?.trim() ||
2386
+ new DeviceAuthClient().api_token?.trim() ||
2387
+ null;
2388
+ const apiKeySource = options.api_key !== undefined
2389
+ ? 'argument'
2390
+ : process.env.BROWSER_USE_API_KEY?.trim()
2391
+ ? 'environment'
2392
+ : new DeviceAuthClient().api_token?.trim()
2393
+ ? 'cloud_auth'
2394
+ : null;
2395
+ const tunnelStatus = options.cloudflared_path !== undefined
2396
+ ? options.cloudflared_path
2397
+ ? {
2398
+ available: true,
2399
+ source: 'system',
2400
+ path: options.cloudflared_path,
2401
+ note: 'cloudflared installed',
2402
+ }
2403
+ : {
2404
+ available: false,
2405
+ source: null,
2406
+ path: null,
2407
+ note: 'cloudflared not installed - install it manually before using tunnel',
2408
+ }
2409
+ : get_tunnel_manager().get_status();
2410
+ const checks = {
2411
+ package: {
2412
+ status: 'ok',
2413
+ message: `browser-use ${options.version ?? get_browser_use_version()}`,
2414
+ },
2415
+ browser: (() => {
2416
+ const executable = options.browser_executable !== undefined
2417
+ ? options.browser_executable
2418
+ : systemChrome.findExecutable();
2419
+ if (executable) {
2420
+ return {
2421
+ status: 'ok',
2422
+ message: `Chrome detected at ${executable}`,
2423
+ };
2424
+ }
2425
+ return {
2426
+ status: 'warning',
2427
+ message: 'Chrome executable not detected',
2428
+ note: 'Local browser launch may fail until Chrome or Chromium is installed.',
2429
+ };
2430
+ })(),
2431
+ api_key: (() => {
2432
+ if (configuredApiKey) {
2433
+ return {
2434
+ status: 'ok',
2435
+ message: apiKeySource === 'cloud_auth'
2436
+ ? 'Browser Use API key is configured in cloud auth'
2437
+ : 'BROWSER_USE_API_KEY is configured',
2438
+ };
2439
+ }
2440
+ return {
2441
+ status: 'missing',
2442
+ message: 'BROWSER_USE_API_KEY is not configured',
2443
+ note: 'Required for browser-use cloud browser features.',
2444
+ };
2445
+ })(),
2446
+ cloudflared: (() => {
2447
+ if (tunnelStatus.available && tunnelStatus.path) {
2448
+ return {
2449
+ status: 'ok',
2450
+ message: `cloudflared detected at ${tunnelStatus.path}`,
2451
+ };
2452
+ }
2453
+ return {
2454
+ status: 'missing',
2455
+ message: 'cloudflared not found',
2456
+ note: tunnelStatus.note ||
2457
+ 'Tunnel features remain unavailable until cloudflared is installed.',
2458
+ };
2459
+ })(),
2460
+ network: {
2461
+ status: 'warning',
2462
+ message: 'Network connectivity check inconclusive',
2463
+ note: 'Some remote features may not work offline.',
2464
+ },
2465
+ };
2466
+ try {
2467
+ const response = await fetchImpl('https://api.github.com', {
2468
+ method: 'HEAD',
2469
+ });
2470
+ if (response.ok || response.status < 500) {
2471
+ checks.network = {
2472
+ status: 'ok',
2473
+ message: 'Network connectivity OK',
2474
+ };
2475
+ }
2476
+ }
2477
+ catch {
2478
+ checks.network = {
2479
+ status: 'warning',
2480
+ message: 'Network connectivity check inconclusive',
2481
+ note: 'Some remote features may not work offline.',
2482
+ };
2483
+ }
2484
+ const allOk = Object.values(checks).every((check) => check.status === 'ok');
2485
+ return {
2486
+ status: allOk ? 'healthy' : 'issues_found',
2487
+ checks,
2488
+ summary: summarizeDoctorChecks(checks),
2489
+ };
2490
+ };
2491
+ const printDoctorReport = (report) => {
2492
+ console.log(`status: ${report.status}`);
2493
+ console.log(`summary: ${report.summary}`);
2494
+ for (const [name, check] of Object.entries(report.checks)) {
2495
+ console.log(`${name}: ${check.status} - ${check.message}`);
2496
+ if (check.note) {
2497
+ console.log(` note: ${check.note}`);
2498
+ }
2499
+ if (check.fix) {
2500
+ console.log(` fix: ${check.fix}`);
2501
+ }
2502
+ }
2503
+ };
768
2504
  async function runMcpServer() {
769
2505
  const server = new MCPServer('browser-use', get_browser_use_version());
770
2506
  await server.start();
@@ -777,6 +2513,50 @@ async function runMcpServer() {
777
2513
  await new Promise(() => { });
778
2514
  }
779
2515
  export async function main(argv = process.argv.slice(2)) {
2516
+ const prefixedSubcommand = extractPrefixedSubcommand(argv);
2517
+ if (prefixedSubcommand) {
2518
+ if (prefixedSubcommand.debug) {
2519
+ enableDebugLogging();
2520
+ }
2521
+ if (prefixedSubcommand.command === 'run') {
2522
+ if (!hasCloudRunFlags(prefixedSubcommand.argv) ||
2523
+ !hasExplicitRemoteRunFlag(prefixedSubcommand.argv)) {
2524
+ await main([
2525
+ ...prefixedSubcommand.forwardedArgs,
2526
+ ...prefixedSubcommand.argv,
2527
+ ]);
2528
+ return;
2529
+ }
2530
+ const exitCode = await runCloudTaskCommand(prefixedSubcommand.argv);
2531
+ if (exitCode !== 0) {
2532
+ process.exit(exitCode);
2533
+ }
2534
+ return;
2535
+ }
2536
+ const subcommandArgv = [
2537
+ ...prefixedSubcommand.argv,
2538
+ ...prefixedSubcommand.forwardedArgs,
2539
+ ];
2540
+ if (prefixedSubcommand.command === 'task') {
2541
+ const exitCode = await runTaskCommand(subcommandArgv);
2542
+ if (exitCode !== 0) {
2543
+ process.exit(exitCode);
2544
+ }
2545
+ return;
2546
+ }
2547
+ if (prefixedSubcommand.command === 'session') {
2548
+ const exitCode = await runSessionCommand(subcommandArgv);
2549
+ if (exitCode !== 0) {
2550
+ process.exit(exitCode);
2551
+ }
2552
+ return;
2553
+ }
2554
+ const exitCode = await runProfileCommand(subcommandArgv);
2555
+ if (exitCode !== 0) {
2556
+ process.exit(exitCode);
2557
+ }
2558
+ return;
2559
+ }
780
2560
  let args;
781
2561
  try {
782
2562
  args = parseCliArgs(argv);
@@ -796,13 +2576,45 @@ export async function main(argv = process.argv.slice(2)) {
796
2576
  return;
797
2577
  }
798
2578
  if (args.debug) {
799
- process.env.BROWSER_USE_LOGGING_LEVEL = 'debug';
800
- setupLogging({ logLevel: 'debug', forceSetup: true });
2579
+ enableDebugLogging();
801
2580
  }
802
2581
  if (args.mcp) {
803
2582
  await runMcpServer();
804
2583
  return;
805
2584
  }
2585
+ if (args.prompt == null && args.positional[0] === 'doctor') {
2586
+ const report = await runDoctorChecks();
2587
+ printDoctorReport(report);
2588
+ return;
2589
+ }
2590
+ if (args.prompt == null && args.positional[0] === 'install') {
2591
+ console.log('Installing Chromium browser...');
2592
+ runInstallCommand();
2593
+ console.log('Chromium browser installed.');
2594
+ return;
2595
+ }
2596
+ if (args.prompt == null && args.positional[0] === 'setup') {
2597
+ const exitCode = await runSetupCommand({
2598
+ mode: args.setup_mode,
2599
+ yes: args.yes,
2600
+ api_key: args.api_key,
2601
+ }, {
2602
+ json_output: args.json,
2603
+ });
2604
+ if (exitCode !== 0) {
2605
+ process.exit(exitCode);
2606
+ }
2607
+ return;
2608
+ }
2609
+ if (args.prompt == null && args.positional[0] === 'tunnel') {
2610
+ const exitCode = await runTunnelCommand(args.positional.slice(1), {
2611
+ json_output: args.json,
2612
+ });
2613
+ if (exitCode !== 0) {
2614
+ process.exit(exitCode);
2615
+ }
2616
+ return;
2617
+ }
806
2618
  const task = resolveTask(args);
807
2619
  const shouldStartInteractive = shouldStartInteractiveMode(task);
808
2620
  if (!task && !shouldStartInteractive) {