UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

228 lines • 8.99 kB
#!/usr/bin/env node /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ process.title = 'chrome-devtools'; import process from 'node:process'; import { startDaemon, stopDaemon, sendCommand, handleResponse, verifyDaemonVersion, } from '../daemon/client.js'; import { isDaemonRunning, serializeArgs, assertValidSessionId, } from '../daemon/utils.js'; import { logDisclaimers } from '../index.js'; import { hideBin, yargs } from '../third_party/index.js'; import { checkForUpdates } from '../utils/check-for-updates.js'; import { VERSION } from '../version.js'; import { commands } from '../config/cli-options.js'; import { mcpOptions, parseArguments, getMcpOptionsForViaCli, } from '../config/mcp-options.js'; await checkForUpdates('Run `npm install -g chrome-devtools-mcp@latest` and `chrome-devtools start` to update and restart the daemon.'); const DEFAULT_CLI_ARGS = ['--viaCli']; async function start(args, sessionId) { const combinedArgs = [...DEFAULT_CLI_ARGS, ...args]; await startDaemon(combinedArgs, sessionId); logDisclaimers(parseArguments(VERSION, combinedArgs)); } function getCliOptions() { const options = { ...getMcpOptionsForViaCli(), }; // Missing CLI serialization. delete options.viewport; // Change the defaults for the CLI. delete options.experimentalStructuredContent; delete options.experimentalInteropTools; return options; } const y = yargs(hideBin(process.argv)) .locale('en') // Force English to ensure error string matching works in .fail, all custom messages we output are in English anyways .scriptName('chrome-devtools') .showHelpOnFail(true) .usage('chrome-devtools <command> [...args] --flags') .usage(`Run 'chrome-devtools <command> --help' for help on the specific command.`) .option('sessionId', { type: 'string', description: 'Session ID for daemon scoping', default: '', hidden: true, coerce: (sessionId) => { assertValidSessionId(sessionId); return sessionId; }, }) .demandCommand() .version(VERSION) .strict() .help(true) .wrap(120) .fail((msg, err) => { if (msg) { console.error('Error:', msg); if (msg.includes('Not enough non-option arguments') || msg.includes('Unknown argument') || msg.includes('Unknown arguments')) { console.error('\n========================================='); console.error('💡 TIP FOR AI AGENT / DEVELOPER:'); console.error('In the `chrome-devtools` CLI:'); console.error('1. Required parameters MUST be passed as positional arguments (without flags).'); console.error(' - INCORRECT: chrome-devtools click --pageId 1 --uid "1_2"'); console.error(' - CORRECT: chrome-devtools click 1 "1_2"'); console.error('2. Optional parameters are passed as double-dash options/flags (e.g. --dblClick true).'); console.error('3. Make sure to escape quotes properly for your shell environment.'); console.error('Run `chrome-devtools <command> --help` to see exact positional and optional parameters.'); console.error('========================================='); } } else if (err) { console.error(err); } process.exit(1); }); y.command('start', 'Start or restart chrome-devtools-mcp', y => y .options(getCliOptions()) .example('$0 start --browserUrl http://localhost:9222', 'Start the server connecting to an existing browser') .strict(), async (argv) => { if (isDaemonRunning(argv.sessionId)) { await stopDaemon(argv.sessionId); } // Defaults but we do not want to affect the yargs conflict resolution. if (argv.isolated === undefined && argv.userDataDir === undefined && !argv.autoConnect && !argv.browserUrl && !argv.wsEndpoint) { argv.isolated = true; } if (argv.headless === undefined && !argv.autoConnect && !argv.browserUrl && !argv.wsEndpoint) { argv.headless = true; } const args = serializeArgs(mcpOptions, argv); await start(args, argv.sessionId); process.exit(0); }).strict(); // Re-enable strict validation for other commands; this is applied to the yargs instance itself y.command('status', 'Checks if chrome-devtools-mcp is running', y => y, async (argv) => { if (isDaemonRunning(argv.sessionId)) { console.log('chrome-devtools-mcp daemon is running.'); const response = await sendCommand({ method: 'status', }, argv.sessionId); if (response.success) { const data = JSON.parse(response.result); console.log(`pid=${data.pid} socket=${data.socketPath} start-date=${data.startDate} version=${data.version}`); console.log(`args=${JSON.stringify(data.args)}`); if (data.version !== VERSION) { console.warn(`Warning: Daemon server version (${data.version}) does not match CLI version (${VERSION}). Run 'chrome-devtools start' to update and restart the daemon.`); } } else { console.error('Error:', response.error); process.exit(1); } } else { console.log('chrome-devtools-mcp daemon is not running.'); } process.exit(0); }); y.command('stop', 'Stop chrome-devtools-mcp if any', y => y, async (argv) => { const sessionId = argv.sessionId; if (!isDaemonRunning(sessionId)) { process.exit(0); } await stopDaemon(sessionId); process.exit(0); }); for (const [commandName, commandDef] of Object.entries(commands)) { const args = commandDef.args; const requiredArgNames = Object.keys(args).filter(name => args[name].required); const optionalArgNames = Object.keys(args).filter(name => !args[name].required); let commandStr = commandName; for (const arg of requiredArgNames) { commandStr += ` <${arg}>`; } for (const arg of optionalArgNames) { commandStr += ` [--${arg}]`; } y.command(commandStr, commandDef.description, y => { y.option('output-format', { choices: ['md', 'json'], default: 'md', }); for (const [argName, opt] of Object.entries(args)) { const type = opt.type === 'integer' || opt.type === 'number' ? 'number' : opt.type === 'boolean' ? 'boolean' : opt.type === 'array' ? 'array' : 'string'; if (opt.required) { const options = { describe: opt.description, type: type, }; if (opt.default !== undefined) { options.default = opt.default; } if (opt.enum) { options.choices = opt.enum; } y.positional(argName, options); } else { const options = { describe: opt.description, type: type, }; if (opt.default !== undefined) { options.default = opt.default; } if (opt.enum) { options.choices = opt.enum; } y.option(argName, options); } } }, async (argv) => { const sessionId = argv.sessionId; try { const versionWarningPromise = isDaemonRunning(sessionId) ? verifyDaemonVersion(sessionId, VERSION) : Promise.resolve(undefined); if (!isDaemonRunning(sessionId)) { await start(serializeArgs(mcpOptions, argv), sessionId); } const commandArgs = {}; for (const argName of Object.keys(args)) { if (argName in argv) { commandArgs[argName] = argv[argName]; } } const response = await sendCommand({ method: 'invoke_tool', tool: commandName, args: commandArgs, }, sessionId); if (response.success) { console.log(await handleResponse(JSON.parse(response.result), argv['output-format'])); } else { console.error('Error:', response.error); } const versionWarning = await versionWarningPromise; if (versionWarning) { console.warn(versionWarning); } if (!response.success) { process.exit(1); } } catch (error) { console.error('Failed to execute command:', error); process.exit(1); } }); } await y.parse(); //# sourceMappingURL=chrome-devtools.js.map