browser-devtools-mcp 0.2.2 → 0.2.4

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 (50) hide show
  1. package/README.md +206 -29
  2. package/dist/cli/index.d.ts +1 -0
  3. package/dist/cli/runner.js +158 -0
  4. package/dist/core-B3VLZZCP.js +1 -0
  5. package/dist/core-IV5QBQ2N.js +13 -0
  6. package/dist/core-NLBNZBEB.js +1124 -0
  7. package/dist/daemon-server.js +1 -1
  8. package/dist/index.js +2 -209
  9. package/dist/platform/browser/cli/runner.js +2 -0
  10. package/dist/platform/browser/index.d.ts +2 -0
  11. package/dist/platform/browser/tools/a11y/index.d.ts +2 -0
  12. package/dist/platform/browser/tools/content/index.d.ts +2 -0
  13. package/dist/platform/browser/tools/debug/index.d.ts +2 -0
  14. package/dist/platform/browser/tools/figma/index.d.ts +2 -0
  15. package/dist/platform/browser/tools/index.d.ts +5 -0
  16. package/dist/platform/browser/tools/interaction/index.d.ts +2 -0
  17. package/dist/platform/browser/tools/navigation/index.d.ts +2 -0
  18. package/dist/platform/browser/tools/o11y/index.d.ts +2 -0
  19. package/dist/platform/browser/tools/react/index.d.ts +2 -0
  20. package/dist/platform/browser/tools/run/index.d.ts +2 -0
  21. package/dist/platform/browser/tools/stub/index.d.ts +2 -0
  22. package/dist/platform/browser/tools/sync/index.d.ts +2 -0
  23. package/dist/platform/index.d.ts +3 -0
  24. package/dist/platform/node/cli/runner.js +2 -0
  25. package/dist/platform/node/entry.js +2 -0
  26. package/dist/platform/node/index.d.ts +11 -0
  27. package/dist/platform/node/tools/debug/index.d.ts +6 -0
  28. package/dist/platform/node/tools/index.d.ts +10 -0
  29. package/dist/platform/node/tools/run/index.d.ts +2 -0
  30. package/dist/platform/types.d.ts +15 -0
  31. package/dist/tools/index.d.ts +1 -4
  32. package/dist/tools/types.d.ts +8 -3
  33. package/package.json +6 -2
  34. package/dist/cli.js +0 -179
  35. package/dist/core.js +0 -764
  36. package/dist/tools/a11y/index.d.ts +0 -2
  37. package/dist/tools/content/index.d.ts +0 -2
  38. package/dist/tools/debug/index.d.ts +0 -2
  39. package/dist/tools/figma/index.d.ts +0 -2
  40. package/dist/tools/interaction/index.d.ts +0 -2
  41. package/dist/tools/navigation/index.d.ts +0 -2
  42. package/dist/tools/o11y/index.d.ts +0 -2
  43. package/dist/tools/react/index.d.ts +0 -2
  44. package/dist/tools/run/index.d.ts +0 -2
  45. package/dist/tools/stub/index.d.ts +0 -2
  46. package/dist/tools/sync/index.d.ts +0 -2
  47. /package/dist/{otel → platform/browser/otel}/otel-initializer.bundle.js +0 -0
  48. /package/dist/{tools → platform/browser/tools}/figma/compare/index.d.ts +0 -0
  49. /package/dist/{tools → platform/browser/tools}/figma/compare/types.d.ts +0 -0
  50. /package/dist/{types.d.ts → platform/browser/types.d.ts} +0 -0
package/dist/cli.js DELETED
@@ -1,179 +0,0 @@
1
- #!/usr/bin/env node
2
- import{E as U,H as T,a as p,f as G,g as Z,h as B,i as M,j as X,k as Q,l as oo,m as eo,n as no,o as so,p as to,q as ro,r as io,s as ao,t as lo,u as co,v as mo,w as po,x as H,y as uo,z as go}from"./core.js";import{spawn as Ao,execSync as No}from"child_process";import{createRequire as Io}from"module";import*as V from"path";import*as K from"readline";import{fileURLToPath as Po}from"url";import{Command as R,Option as $}from"commander";import{Command as fo,Option as O}from"commander";import{ZodFirstPartyTypeKind as _}from"zod";function Eo(o){let s=o,a=!1,d;for(;;){let g=s._def.typeName;if(g===_.ZodOptional)a=!0,s=s._def.innerType;else if(g===_.ZodDefault)a=!0,d=s._def.defaultValue(),s=s._def.innerType;else if(g===_.ZodNullable)a=!0,s=s._def.innerType;else break}return{innerType:s,isOptional:a,defaultValue:d}}p(Eo,"_unwrapZodType");function $o(o){return o._def.description}p($o,"_getDescription");function vo(o){return o.replace(/[-_]([a-z])/g,(s,a)=>a.toUpperCase())}p(vo,"_toCamelCase");function To(o){return o.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}p(To,"_toKebabCase");function Oo(o,s){let{innerType:a,isOptional:d,defaultValue:g}=Eo(s),m=$o(s)||`The ${o} value`,w=To(o),D=a._def.typeName,f;switch(D){case _.ZodString:f=new O(`--${w} <string>`,m);break;case _.ZodNumber:f=new O(`--${w} <number>`,m),f.argParser(l=>{let c=Number(l);if(!Number.isFinite(c))throw new Error(`Invalid number: ${l}`);return c});break;case _.ZodBoolean:f=new O(`--${w}`,m);break;case _.ZodEnum:let A=a._def.values;f=new O(`--${w} <choice>`,m).choices(A);break;case _.ZodArray:f=new O(`--${w} <value...>`,m);break;case _.ZodObject:case _.ZodRecord:f=new O(`--${w} <json>`,m),f.argParser(l=>{try{return JSON.parse(l)}catch{throw new Error(`Invalid JSON: ${l}`)}});break;case _.ZodAny:case _.ZodUnknown:f=new O(`--${w} <value>`,m),f.argParser(l=>{try{return JSON.parse(l)}catch{return l}});break;case _.ZodLiteral:let e=a._def.value;typeof e=="boolean"?f=new O(`--${w}`,m):(f=new O(`--${w} <value>`,m),f.default(e));break;case _.ZodUnion:let t=a._def.options;if(t.every(l=>l._def.typeName===_.ZodLiteral)){let l=t.map(c=>String(c._def.value));f=new O(`--${w} <choice>`,m).choices(l)}else f=new O(`--${w} <value>`,m),f.argParser(l=>{try{return JSON.parse(l)}catch{return l}});break;default:f=new O(`--${w} <value>`,m);break}return g!==void 0&&f.default(g),!d&&g===void 0&&f.makeOptionMandatory(!0),f}p(Oo,"_createOption");function Ro(o){let s=[];for(let[a,d]of Object.entries(o)){let g=Oo(a,d);g&&s.push(g)}return s}p(Ro,"_generateOptionsFromSchema");function Do(o){let s={};for(let[a,d]of Object.entries(o)){let g=vo(a);d!==void 0&&(s[g]=d)}return s}p(Do,"_parseOptionsToToolInput");function Co(o){let s=o.indexOf("_");return s===-1?{domain:"default",commandName:o}:{domain:o.substring(0,s),commandName:o.substring(s+1)}}p(Co,"_parseToolName");function Y(o,s,a){let d=new Map;for(let g of s){let{domain:m,commandName:w}=Co(g.name()),D=d.get(m);D||(D=new fo(m).description(`${m.charAt(0).toUpperCase()+m.slice(1)} commands`),d.set(m,D),o.addCommand(D));let f=new fo(w).description(g.description().trim()),A=Ro(g.inputSchema());for(let e of A)f.addOption(e);f.action(async e=>{let t=Do(e),r=o.opts();await a(g.name(),t,r)}),D.addCommand(f)}}p(Y,"registerToolCommands");var W=Io(import.meta.url),Lo=Po(import.meta.url),ko=V.dirname(Lo),E=3e4,ho=!1;function h(o,s){if(ho){let a=new Date().toISOString();s!==void 0?console.error(`[${a}] [DEBUG] ${o}`,s):console.error(`[${a}] [DEBUG] ${o}`)}}p(h,"_verbose");async function v(o){h(`Checking if daemon is running on port ${o}`);try{let s=await fetch(`http://localhost:${o}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(s.ok){let d=(await s.json()).status==="ok";return h(`Daemon health check result: ${d?"running":"not running"}`),d}return h(`Daemon health check failed: HTTP ${s.status}`),!1}catch(s){return h(`Daemon health check error: ${s.message}`),!1}}p(v,"_isDaemonRunning");function xo(o){let s={...process.env};return o.headless!==void 0&&(s.BROWSER_HEADLESS_ENABLE=String(o.headless)),o.persistent!==void 0&&(s.BROWSER_PERSISTENT_ENABLE=String(o.persistent)),o.userDataDir!==void 0&&(s.BROWSER_PERSISTENT_USER_DATA_DIR=o.userDataDir),o.useSystemBrowser!==void 0&&(s.BROWSER_USE_INSTALLED_ON_SYSTEM=String(o.useSystemBrowser)),o.browserPath!==void 0&&(s.BROWSER_EXECUTABLE_PATH=o.browserPath),s}p(xo,"_buildDaemonEnv");function k(o){let s=V.join(ko,"daemon-server.js"),a=xo(o);h(`Starting daemon server from: ${s}`),h(`Daemon port: ${o.port}`),h("Environment variables:",{BROWSER_HEADLESS_ENABLE:a.BROWSER_HEADLESS_ENABLE,BROWSER_PERSISTENT_ENABLE:a.BROWSER_PERSISTENT_ENABLE,BROWSER_USE_INSTALLED_ON_SYSTEM:a.BROWSER_USE_INSTALLED_ON_SYSTEM});let d=Ao(process.execPath,[s,"--port",String(o.port)],{detached:!0,stdio:"ignore",env:a});d.unref(),h(`Daemon process spawned with PID: ${d.pid}`),o.quiet||U(`Started daemon server as detached process (PID: ${d.pid})`)}p(k,"_startDaemonDetached");async function I(o){if(await v(o.port))h("Daemon is already running");else{o.quiet||U(`Daemon server is not running on port ${o.port}, starting...`),k(o);let a=10,d=500;h(`Waiting for daemon to be ready (max ${a} retries, ${d}ms delay)`);for(let g=0;g<a;g++)if(await new Promise(m=>setTimeout(m,d)),h(`Retry ${g+1}/${a}: checking daemon status...`),await v(o.port)){h("Daemon is now ready"),o.quiet||U("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${a*d/1e3} seconds`)}}p(I,"_ensureDaemonRunning");async function F(o,s){try{return(await fetch(`http://localhost:${o}/shutdown`,{method:"POST",signal:AbortSignal.timeout(s)})).ok}catch{return!1}}p(F,"_stopDaemon");async function bo(o,s,a,d,g){let m={"Content-Type":"application/json"};d&&(m["session-id"]=d);let w={toolName:s,toolInput:a};h(`Calling tool: ${s}`),h("Tool input:",a),h(`Session ID: ${d||"(default)"}`),h(`Timeout: ${g||"none"}`);let D=Date.now(),f=await fetch(`http://localhost:${o}/call`,{method:"POST",headers:m,body:JSON.stringify(w),signal:g?AbortSignal.timeout(g):void 0}),A=Date.now()-D;if(h(`Tool call completed in ${A}ms, status: ${f.status}`),!f.ok){let t=await f.json().catch(()=>({}));throw h("Tool call error:",t),new Error(t?.error?.message||`HTTP ${f.status}: ${f.statusText}`)}let e=await f.json();return h("Tool call result:",e.toolError?{error:e.toolError}:{success:!0}),e}p(bo,"_callTool");async function jo(o,s,a){try{return(await fetch(`http://localhost:${o}/session`,{method:"DELETE",headers:{"session-id":s},signal:AbortSignal.timeout(a)})).ok}catch{return!1}}p(jo,"_deleteSession");async function J(o,s){try{let a=await fetch(`http://localhost:${o}/info`,{method:"GET",signal:AbortSignal.timeout(s)});return a.ok?await a.json():null}catch{return null}}p(J,"_getDaemonInfo");async function yo(o,s){try{let a=await fetch(`http://localhost:${o}/sessions`,{method:"GET",signal:AbortSignal.timeout(s)});return a.ok?await a.json():null}catch{return null}}p(yo,"_listSessions");async function Go(o,s,a){try{let d=await fetch(`http://localhost:${o}/session`,{method:"GET",headers:{"session-id":s},signal:AbortSignal.timeout(a)});return d.ok?await d.json():null}catch{return null}}p(Go,"_getSessionInfo");function P(o){let s=Math.floor(o/86400),a=Math.floor(o%86400/3600),d=Math.floor(o%3600/60),g=o%60,m=[];return s>0&&m.push(`${s}d`),a>0&&m.push(`${a}h`),d>0&&m.push(`${d}m`),m.push(`${g}s`),m.join(" ")}p(P,"_formatUptime");function q(o){return new Date(o).toISOString()}p(q,"_formatTimestamp");function L(o){let s=o._def.typeName;return s==="ZodOptional"||s==="ZodNullable"||s==="ZodDefault"?L(o._def.innerType):s==="ZodArray"?`${L(o._def.type)}[]`:s==="ZodEnum"?o._def.values.join(" | "):s==="ZodLiteral"?JSON.stringify(o._def.value):s==="ZodUnion"?o._def.options.map(d=>L(d)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[s]||s.replace("Zod","").toLowerCase()}p(L,"_getZodTypeName");function z(o){if(o._def.description)return o._def.description;if(o._def.typeName==="ZodOptional"||o._def.typeName==="ZodNullable"||o._def.typeName==="ZodDefault")return z(o._def.innerType)}p(z,"_getZodDescription");function Zo(o){let s=o._def.typeName;return s==="ZodOptional"||s==="ZodNullable"}p(Zo,"_isZodOptional");function wo(o){return o._def.typeName==="ZodDefault"?!0:o._def.typeName==="ZodOptional"||o._def.typeName==="ZodNullable"?wo(o._def.innerType):!1}p(wo,"_hasZodDefault");function So(o){if(o._def.typeName==="ZodDefault")return o._def.defaultValue();if(o._def.typeName==="ZodOptional"||o._def.typeName==="ZodNullable")return So(o._def.innerType)}p(So,"_getZodDefault");function x(o,s=0){let a=" ".repeat(s);if(o==null)return`${a}(empty)`;if(typeof o=="string")return o.split(`
3
- `).map(d=>`${a}${d}`).join(`
4
- `);if(typeof o=="number"||typeof o=="boolean")return`${a}${o}`;if(Array.isArray(o))return o.length===0?`${a}[]`:o.map(d=>x(d,s)).join(`
5
- `);if(typeof o=="object"){let d=[];for(let[g,m]of Object.entries(o))m!==void 0&&(typeof m=="object"&&m!==null&&!Array.isArray(m)?(d.push(`${a}${g}:`),d.push(x(m,s+1))):Array.isArray(m)?(d.push(`${a}${g}:`),d.push(x(m,s+1))):d.push(`${a}${g}: ${m}`));return d.join(`
6
- `)}return`${a}${String(o)}`}p(x,"_formatOutput");function b(o,s,a=!1){let d=s?JSON.stringify(o,null,2):String(o);a?console.error(d):console.log(d)}p(b,"_printOutput");function Bo(o){return o.addOption(new $("--port <number>","Daemon server port").argParser(s=>{let a=Number(s);if(!Number.isInteger(a)||a<1||a>65535)throw new Error("Port must be an integer between 1 and 65535");return a}).default(H)).addOption(new $("--session-id <string>","Session ID for maintaining browser state across commands")).addOption(new $("--json","Output results as JSON")).addOption(new $("--quiet","Suppress log messages, only show output")).addOption(new $("--verbose","Enable verbose/debug output")).addOption(new $("--timeout <ms>","Timeout for operations in milliseconds").argParser(s=>{let a=Number(s);if(!Number.isFinite(a)||a<0)throw new Error("Timeout must be a positive number");return a}).default(E)).addOption(new $("--headless","Run browser in headless mode (no visible window)").default(G)).addOption(new $("--no-headless","Run browser in headful mode (visible window)")).addOption(new $("--persistent","Use persistent browser context (preserves cookies, localStorage)").default(Z)).addOption(new $("--no-persistent","Use ephemeral browser context (cleared on session end)")).addOption(new $("--user-data-dir <path>","Directory for persistent browser context user data").default(B)).addOption(new $("--use-system-browser","Use system-installed Chrome instead of bundled browser").default(M)).addOption(new $("--browser-path <path>","Custom browser executable path"))}p(Bo,"_addGlobalOptions");async function Mo(){let o=Bo(new R("browser-devtools-cli").description("Browser DevTools MCP CLI").version(W("../package.json").version));o.hook("preAction",e=>{e.opts().verbose&&(ho=!0,h("Verbose mode enabled"),h("CLI version:",W("../package.json").version),h("Node version:",process.version),h("Platform:",process.platform))});let s=new R("daemon").description("Manage the daemon server");s.command("start").description("Start the daemon server").action(async()=>{let e=o.opts();if(await v(e.port)){e.json?b({status:"already_running",port:e.port},!0):e.quiet||console.log(`Daemon server is already running on port ${e.port}`);return}k(e);let r=10,l=500;for(let c=0;c<r;c++)if(await new Promise(n=>setTimeout(n,l)),await v(e.port)){e.json?b({status:"started",port:e.port},!0):e.quiet||console.log(`Daemon server started on port ${e.port}`);return}e.json?b({status:"failed",error:"Daemon server failed to start"},!0,!0):console.error("Failed to start daemon server"),process.exit(1)}),s.command("stop").description("Stop the daemon server").action(async()=>{let e=o.opts();if(!await v(e.port)){e.json?b({status:"not_running",port:e.port},!0):e.quiet||console.log(`Daemon server is not running on port ${e.port}`);return}await F(e.port,e.timeout??E)?e.json?b({status:"stopped",port:e.port},!0):e.quiet||console.log(`Daemon server stopped on port ${e.port}`):(e.json?b({status:"failed",error:"Failed to stop daemon server"},!0,!0):console.error("Failed to stop daemon server"),process.exit(1))}),s.command("restart").description("Restart the daemon server (stop + start)").action(async()=>{let e=o.opts(),t=await v(e.port);t&&(h("Stopping daemon server..."),await F(e.port,e.timeout??E)||(e.json?b({status:"failed",error:"Failed to stop daemon server"},!0,!0):console.error("Failed to stop daemon server"),process.exit(1)),h("Waiting for port to be released..."),await new Promise(n=>setTimeout(n,1e3))),h("Starting daemon server..."),k(e);let r=10,l=500;for(let c=0;c<r;c++)if(await new Promise(n=>setTimeout(n,l)),await v(e.port)){e.json?b({status:"restarted",port:e.port},!0):e.quiet||console.log(`Daemon server ${t?"restarted":"started"} on port ${e.port}`);return}e.json?b({status:"failed",error:"Daemon server failed to start"},!0,!0):console.error("Failed to start daemon server"),process.exit(1)}),s.command("status").description("Check daemon server status").action(async()=>{let e=o.opts(),t=await v(e.port);e.json?b({status:t?"running":"stopped",port:e.port},!0):console.log(t?`Daemon server is running on port ${e.port}`:`Daemon server is not running on port ${e.port}`)}),s.command("info").description("Get detailed daemon server information").action(async()=>{let e=o.opts();await v(e.port)||(e.json?b({status:"not_running",port:e.port},!0,!0):console.error(`Daemon server is not running on port ${e.port}`),process.exit(1));let r=await J(e.port,e.timeout??E);r?e.json?b(r,!0):(console.log("Daemon Server Information:"),console.log(` Version: ${r.version}`),console.log(` Port: ${r.port}`),console.log(` Uptime: ${P(r.uptime)}`),console.log(` Sessions: ${r.sessionCount}`)):(e.json?b({status:"error",error:"Failed to get daemon info"},!0,!0):console.error("Failed to get daemon info"),process.exit(1))}),o.addCommand(s);let a=new R("session").description("Manage browser sessions");a.command("list").description("List all active sessions").action(async()=>{let e=o.opts();try{await I(e);let t=await yo(e.port,e.timeout??E);if(t)if(e.json)b(t,!0);else if(t.sessions.length===0)console.log("No active sessions");else{console.log(`Active Sessions (${t.sessions.length}):`);for(let r of t.sessions)console.log(` ${r.id}`),console.log(` Created: ${q(r.createdAt)}`),console.log(` Last Active: ${q(r.lastActiveAt)}`),console.log(` Idle: ${P(r.idleSeconds)}`)}else e.json?b({status:"error",error:"Failed to list sessions"},!0,!0):console.error("Failed to list sessions"),process.exit(1)}catch(t){e.json?b({status:"error",error:t.message},!0,!0):console.error(`Error: ${t.message}`),process.exit(1)}}),a.command("info <session-id>").description("Get information about a specific session").action(async e=>{let t=o.opts();try{await I(t);let r=await Go(t.port,e,t.timeout??E);r?t.json?b(r,!0):(console.log(`Session: ${r.id}`),console.log(` Created: ${q(r.createdAt)}`),console.log(` Last Active: ${q(r.lastActiveAt)}`),console.log(` Idle: ${P(r.idleSeconds)}`)):(t.json?b({status:"not_found",sessionId:e},!0,!0):console.error(`Session '${e}' not found`),process.exit(1))}catch(r){t.json?b({status:"error",error:r.message},!0,!0):console.error(`Error: ${r.message}`),process.exit(1)}}),a.command("delete <session-id>").description("Delete a specific session").action(async e=>{let t=o.opts();try{await I(t),await jo(t.port,e,t.timeout??E)?t.json?b({status:"deleted",sessionId:e},!0):t.quiet||console.log(`Session '${e}' deleted`):(t.json?b({status:"not_found",sessionId:e},!0,!0):console.error(`Session '${e}' not found or already deleted`),process.exit(1))}catch(r){t.json?b({status:"error",error:r.message},!0,!0):console.error(`Error: ${r.message}`),process.exit(1)}}),o.addCommand(a);let d=new R("tools").description("List and inspect available tools");d.command("list").description("List all available tools").option("--domain <domain>","Filter by domain (e.g., navigation, content)").action(e=>{let t=o.opts(),r=new Map;for(let l of T){let n=l.name().split("_")[0];e.domain&&n!==e.domain||(r.has(n)||r.set(n,[]),r.get(n).push(l))}if(t.json){let l=[];for(let[c,n]of r)l.push({domain:c,tools:n.map(i=>({name:i.name(),description:i.description().trim().split(`
7
- `)[0]}))});b(l,!0)}else{if(r.size===0){console.log("No tools found");return}console.log(`Available Tools (${T.length} total):
8
- `);for(let[l,c]of r){console.log(` ${l}:`);for(let n of c){let i=n.name().split("_")[1]||n.name(),u=n.description().trim().split(`
9
- `)[0];console.log(` ${i.padEnd(30)} ${u}`)}console.log()}}}),d.command("info <tool-name>").description("Get detailed information about a specific tool").action(e=>{let t=o.opts(),r=T.find(n=>n.name()===e);r||(r=T.find(n=>n.name().split("_")[1]===e)),r||(t.json?b({status:"not_found",toolName:e},!0,!0):console.error(`Tool '${e}' not found`),process.exit(1));let l=r.inputSchema(),c=[];for(let[n,i]of Object.entries(l))c.push({name:n,type:L(i),required:!Zo(i),description:z(i),default:wo(i)?So(i):void 0});if(t.json)b({name:r.name(),description:r.description().trim(),parameters:c},!0);else{let n=r.name().split("_");if(console.log(`Tool: ${r.name()}`),console.log(`Domain: ${n[0]}`),console.log(`
10
- Description:`),console.log(r.description().trim().split(`
11
- `).map(i=>` ${i}`).join(`
12
- `)),console.log(`
13
- Parameters:`),c.length===0)console.log(" (none)");else for(let i of c){let u=i.required?"(required)":"(optional)";console.log(` --${i.name} <${i.type}> ${u}`),i.description&&console.log(` ${i.description}`),i.default!==void 0&&console.log(` Default: ${JSON.stringify(i.default)}`)}console.log(`
14
- Usage:`),console.log(` browser-devtools-cli ${n[0]} ${n[1]||r.name()} [options]`)}}),d.command("search <query>").description("Search tools by name or description").action(e=>{let t=o.opts(),r=e.toLowerCase(),l=T.filter(c=>{let n=c.name().toLowerCase(),i=c.description().toLowerCase();return n.includes(r)||i.includes(r)});if(t.json){let c=l.map(n=>{let i=n.name().split("_");return{name:n.name(),domain:i[0],description:n.description().trim().split(`
15
- `)[0]}});b(c,!0)}else{if(l.length===0){console.log(`No tools found matching "${e}"`);return}console.log(`Tools matching "${e}" (${l.length} found):
16
- `);for(let c of l){let n=c.name().split("_"),i=n[0],u=n[1]||c.name(),y=c.description().trim().split(`
17
- `)[0];console.log(` ${i}/${u}`),console.log(` ${y}
18
- `)}}}),o.addCommand(d);let g=new R("config").description("Show current configuration").action(()=>{let e=o.opts(),t={daemon:{port:H,sessionIdleSeconds:uo,sessionIdleCheckSeconds:go},browser:{headless:G,persistent:Z,userDataDir:B,useSystemBrowser:M,executablePath:X},otel:{enabled:Q,serviceName:oo,serviceVersion:eo,exporterType:no,exporterHttpUrl:so},aws:{region:to,profile:ro},bedrock:{enabled:io,imageEmbedModelId:ao,textEmbedModelId:lo,visionModelId:co},figma:{accessToken:mo?"***":void 0,apiBaseUrl:po}};e.json?b(t,!0):(console.log(`Current Configuration:
19
- `),console.log(" Daemon:"),console.log(` Port: ${t.daemon.port}`),console.log(` Session Idle (sec): ${t.daemon.sessionIdleSeconds}`),console.log(` Idle Check Interval: ${t.daemon.sessionIdleCheckSeconds}`),console.log(`
20
- Browser:`),console.log(` Headless: ${t.browser.headless}`),console.log(` Persistent: ${t.browser.persistent}`),console.log(` User Data Dir: ${t.browser.userDataDir||"(default)"}`),console.log(` Use System Browser: ${t.browser.useSystemBrowser}`),console.log(` Executable Path: ${t.browser.executablePath||"(bundled)"}`),console.log(`
21
- OpenTelemetry:`),console.log(` Enabled: ${t.otel.enabled}`),console.log(` Service Name: ${t.otel.serviceName}`),console.log(` Service Version: ${t.otel.serviceVersion||"(not set)"}`),console.log(` Exporter Type: ${t.otel.exporterType}`),console.log(` Exporter HTTP URL: ${t.otel.exporterHttpUrl||"(not set)"}`),console.log(`
22
- AWS:`),console.log(` Region: ${t.aws.region||"(not set)"}`),console.log(` Profile: ${t.aws.profile||"(not set)"}`),console.log(`
23
- Bedrock:`),console.log(` Enabled: ${t.bedrock.enabled}`),console.log(` Image Embed Model ID: ${t.bedrock.imageEmbedModelId||"(not set)"}`),console.log(` Text Embed Model ID: ${t.bedrock.textEmbedModelId||"(not set)"}`),console.log(` Vision Model ID: ${t.bedrock.visionModelId||"(not set)"}`),console.log(`
24
- Figma:`),console.log(` Access Token: ${t.figma.accessToken||"(not set)"}`),console.log(` API Base URL: ${t.figma.apiBaseUrl}`))});o.addCommand(g);let m=new R("completion").description("Generate shell completion scripts");m.command("bash").description("Generate bash completion script").action(()=>{console.log(`
25
- # Browser DevTools CLI bash completion
26
- _browser_devtools_cli_completions() {
27
- local cur="\${COMP_WORDS[COMP_CWORD]}"
28
- local prev="\${COMP_WORDS[COMP_CWORD-1]}"
29
-
30
- # Main commands
31
- local commands="daemon session tools config completion interactive navigation content interaction a11y accessibility o11y react run stub sync figma"
32
-
33
- # Daemon subcommands
34
- local daemon_cmds="start stop restart status info"
35
-
36
- # Session subcommands
37
- local session_cmds="list info delete"
38
-
39
- # Tools subcommands
40
- local tools_cmds="list info search"
41
-
42
- case "\${prev}" in
43
- browser-devtools-cli)
44
- COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
45
- return 0
46
- ;;
47
- daemon)
48
- COMPREPLY=( $(compgen -W "\${daemon_cmds}" -- "\${cur}") )
49
- return 0
50
- ;;
51
- session)
52
- COMPREPLY=( $(compgen -W "\${session_cmds}" -- "\${cur}") )
53
- return 0
54
- ;;
55
- tools)
56
- COMPREPLY=( $(compgen -W "\${tools_cmds}" -- "\${cur}") )
57
- return 0
58
- ;;
59
- esac
60
-
61
- # Global options
62
- if [[ "\${cur}" == -* ]]; then
63
- local opts="--port --session-id --json --quiet --verbose --timeout --headless --no-headless --persistent --no-persistent --user-data-dir --use-system-browser --browser-path --help --version"
64
- COMPREPLY=( $(compgen -W "\${opts}" -- "\${cur}") )
65
- return 0
66
- fi
67
- }
68
-
69
- complete -F _browser_devtools_cli_completions browser-devtools-cli
70
- `),console.error(`
71
- # To enable, add to your ~/.bashrc:`),console.error('# eval "$(browser-devtools-cli completion bash)"')}),m.command("zsh").description("Generate zsh completion script").action(()=>{console.log(`
72
- #compdef browser-devtools-cli
73
-
74
- _browser_devtools_cli() {
75
- local -a commands
76
- commands=(
77
- 'daemon:Manage the daemon server'
78
- 'session:Manage browser sessions'
79
- 'tools:List and inspect available tools'
80
- 'config:Show current configuration'
81
- 'completion:Generate shell completion scripts'
82
- 'interactive:Start interactive REPL mode'
83
- 'navigation:Navigation commands'
84
- 'content:Content extraction commands'
85
- 'interaction:Interaction commands'
86
- 'a11y:Accessibility commands'
87
- 'accessibility:Extended accessibility commands'
88
- 'o11y:Observability commands'
89
- 'react:React debugging commands'
90
- 'run:Script execution commands'
91
- 'stub:HTTP stubbing commands'
92
- 'sync:Synchronization commands'
93
- 'figma:Figma integration commands'
94
- )
95
-
96
- local -a daemon_cmds
97
- daemon_cmds=(
98
- 'start:Start the daemon server'
99
- 'stop:Stop the daemon server'
100
- 'restart:Restart the daemon server'
101
- 'status:Check daemon server status'
102
- 'info:Get detailed daemon info'
103
- )
104
-
105
- local -a session_cmds
106
- session_cmds=(
107
- 'list:List all active sessions'
108
- 'info:Get information about a session'
109
- 'delete:Delete a specific session'
110
- )
111
-
112
- local -a tools_cmds
113
- tools_cmds=(
114
- 'list:List all available tools'
115
- 'info:Get detailed tool information'
116
- 'search:Search tools by keyword'
117
- )
118
-
119
- _arguments -C \\
120
- '--port[Daemon server port]:port' \\
121
- '--session-id[Session ID]:session_id' \\
122
- '--json[Output as JSON]' \\
123
- '--quiet[Suppress log messages]' \\
124
- '--verbose[Enable verbose output]' \\
125
- '--timeout[Operation timeout]:ms' \\
126
- '--headless[Run in headless mode]' \\
127
- '--no-headless[Run in headful mode]' \\
128
- '--persistent[Use persistent context]' \\
129
- '--no-persistent[Use ephemeral context]' \\
130
- '--user-data-dir[User data directory]:path:_files -/' \\
131
- '--use-system-browser[Use system browser]' \\
132
- '--browser-path[Browser executable path]:path:_files' \\
133
- '--help[Show help]' \\
134
- '--version[Show version]' \\
135
- '1: :->cmd' \\
136
- '*:: :->args'
137
-
138
- case "$state" in
139
- cmd)
140
- _describe 'command' commands
141
- ;;
142
- args)
143
- case "$words[1]" in
144
- daemon)
145
- _describe 'subcommand' daemon_cmds
146
- ;;
147
- session)
148
- _describe 'subcommand' session_cmds
149
- ;;
150
- tools)
151
- _describe 'subcommand' tools_cmds
152
- ;;
153
- esac
154
- ;;
155
- esac
156
- }
157
-
158
- _browser_devtools_cli
159
- `),console.error(`
160
- # To enable, add to your ~/.zshrc:`),console.error('# eval "$(browser-devtools-cli completion zsh)"')}),o.addCommand(m);function w(e){let t=new R("repl").exitOverride().configureOutput({writeOut:p(n=>console.log(n.trimEnd()),"writeOut"),writeErr:p(n=>console.error(n.trimEnd()),"writeErr")}),r=new R("daemon").description("Manage daemon server").exitOverride();r.command("start").description("Start the daemon server").action(async()=>{let n=e;await v(n.port)?console.log(`Daemon server is already running on port ${n.port}`):(k(n),await I(n),console.log(`Daemon server started on port ${n.port}`))}),r.command("stop").description("Stop the daemon server").action(async()=>{let n=e;await v(n.port)?await F(n.port,n.timeout??E)?console.log("Daemon server stopped"):console.error("Failed to stop daemon server"):console.log("Daemon server is not running")}),r.command("restart").description("Restart the daemon server").action(async()=>{let n=e;await v(n.port)&&(await F(n.port,n.timeout??E),await new Promise(u=>setTimeout(u,1e3))),k(n),await I(n),console.log(`Daemon server restarted on port ${n.port}`)}),r.command("status").description("Check daemon server status").action(async()=>{let n=e,i=await v(n.port);console.log(i?`Daemon server is running on port ${n.port}`:"Daemon server is not running")}),r.command("info").description("Show daemon server information").action(async()=>{let n=e,i=await J(n.port,n.timeout??E);i?(console.log(`Version: ${i.version}`),console.log(`Uptime: ${P(i.uptime)}`),console.log(`Sessions: ${i.sessionCount}`),console.log(`Port: ${i.port}`)):console.log("Daemon server is not running")}),t.addCommand(r);let l=new R("session").description("Manage browser sessions").exitOverride();l.command("list").description("List active sessions").action(async()=>{let n=e,i=await yo(n.port,n.timeout??E);if(i&&i.sessions.length>0){console.log(`Active sessions: ${i.sessions.length}`);for(let u of i.sessions)console.log(` ${u.id} (idle: ${P(u.idleSeconds)})`)}else console.log("No active sessions")}),l.command("info <session-id>").description("Show session information").action(async n=>{let i=e;try{let u=await fetch(`http://localhost:${i.port}/session`,{method:"GET",headers:{"session-id":n},signal:AbortSignal.timeout(i.timeout??E)});if(u.ok){let y=await u.json();console.log(`Session: ${y.id}`),console.log(`Created: ${new Date(y.createdAt).toISOString()}`),console.log(`Last Active: ${new Date(y.lastActiveAt).toISOString()}`),console.log(`Idle: ${P(y.idleSeconds)}`)}else console.log(`Session not found: ${n}`)}catch(u){console.error(`Error: ${u.message}`)}}),l.command("delete <session-id>").description("Delete a session").action(async n=>{let i=e;try{(await fetch(`http://localhost:${i.port}/session`,{method:"DELETE",headers:{"session-id":n},signal:AbortSignal.timeout(i.timeout??E)})).ok?console.log(`Session deleted: ${n}`):console.log(`Session not found: ${n}`)}catch(u){console.error(`Error: ${u.message}`)}}),t.addCommand(l);let c=new R("tools").description("Discover and inspect available tools").exitOverride();return c.command("list").description("List all available tools").action(()=>{let n=new Set;for(let i of T)n.add(i.name().split("_")[0]);console.log(`Available domains: ${Array.from(n).join(", ")}`),console.log(`Total tools: ${T.length}`)}),c.command("search <query>").description("Search tools by name or description").action(n=>{let i=n.toLowerCase(),u=T.filter(y=>y.name().toLowerCase().includes(i)||y.description().toLowerCase().includes(i));if(u.length>0){console.log(`Found ${u.length} tools:`);for(let y of u)console.log(` ${y.name()} - ${y.description()}`)}else console.log(`No tools found matching "${n}"`)}),c.command("info <tool-name>").description("Show detailed information about a tool").action(n=>{let i=T.find(u=>u.name()===n);if(i){console.log(`
161
- Tool: ${i.name()}`),console.log(`Description: ${i.description()}`),console.log("Input Schema:");let u=i.inputSchema();for(let[y,S]of Object.entries(u)){let N=L(S),j=z(S)||"",C=S.isOptional();console.log(` --${y} <${N}>${C?" (optional)":""} ${j}`)}}else console.log(`Tool not found: ${n}`)}),t.addCommand(c),t.command("config").description("Show current configuration").action(()=>{let n=e;console.log(`
162
- Current Configuration:`),console.log(` port = ${n.port}`),console.log(` session-id = ${n.sessionId||"(auto)"}`),console.log(` headless = ${n.headless??G}`),console.log(` persistent = ${n.persistent??Z}`),console.log(` user-data-dir = ${n.userDataDir||B||"(default)"}`),console.log(` use-system-browser = ${n.useSystemBrowser??M}`),console.log(` browser-path = ${n.browserPath||"(auto)"}`),console.log(` timeout = ${n.timeout??E}`),console.log(` json = ${n.json??!1}`),console.log(` quiet = ${n.quiet??!1}`),console.log(` verbose = ${n.verbose??!1}`),console.log(`
163
- Tip: Start interactive mode with options:`),console.log(" browser-devtools-cli --no-headless interactive")}),t.command("update").description("Check for updates").option("--check","Only check for updates without installing").action(async n=>{let i=W("../package.json").version,u="browser-devtools-mcp";console.log(`Checking for updates...
164
- `);try{let y=await fetch(`https://registry.npmjs.org/${u}/latest`,{signal:AbortSignal.timeout(1e4)});if(!y.ok){console.error("Failed to check for updates");return}let N=(await y.json()).version;console.log(`Current version: ${i}`),console.log(`Latest version: ${N}`),i===N?console.log(`
165
- You are using the latest version!`):(console.log(`
166
- A new version is available!`),n.check||console.log(`Run: npm install -g ${u}@latest`))}catch(y){console.error(`Error checking for updates: ${y.message}`)}}),t.command("status").description("Show daemon status summary").action(async()=>{let n=e,i=await J(n.port,n.timeout??E);console.log(i?`Daemon: running (v${i.version}, uptime: ${P(i.uptime)}, sessions: ${i.sessionCount})`:"Daemon: not running")}),Y(t,T,async(n,i,u)=>{let y=e;try{let S=await bo(y.port,n,i,y.sessionId,y.timeout);S.toolError?console.error(`Error: ${S.toolError.message}`):S.toolOutput&&(y.json?console.log(JSON.stringify(S.toolOutput,null,2)):console.log(x(S.toolOutput)))}catch(S){console.error(`Error: ${S.message}`)}}),t}p(w,"_createReplProgram");function D(e){let t=[],r="",l=!1,c="";for(let n=0;n<e.length;n++){let i=e[n];l?i===c?(l=!1,t.push(r),r=""):r+=i:i==='"'||i==="'"?(l=!0,c=i):i===" "||i===" "?r&&(t.push(r),r=""):r+=i}return r&&t.push(r),t}p(D,"_parseReplInput");let f=new R("interactive").alias("repl").description("Start interactive REPL mode").action(async()=>{let e=o.opts();console.log("Browser DevTools CLI - Interactive Mode"),console.log(`Type "help" for available commands, "exit" to quit
167
- `);try{await I(e)}catch(l){console.error(`Error: ${l.message}`),process.exit(1)}let t=w(e),r=K.createInterface({input:process.stdin,output:process.stdout,prompt:"browser> "});r.prompt(),r.on("line",async l=>{let c=l.trim();if(!c){r.prompt();return}if((c==="exit"||c==="quit")&&(console.log("Goodbye!"),r.close(),process.exit(0)),c==="help"){console.log(`
168
- REPL Commands:`),console.log(" help Show this help"),console.log(" exit, quit Exit interactive mode"),console.log(`
169
- Available Commands:`),console.log(" status Show daemon status summary"),console.log(" config Show current configuration"),console.log(" update Check for CLI updates"),console.log(" daemon <cmd> Daemon management (start, stop, restart, status, info)"),console.log(" session <cmd> Session management (list, info, delete)"),console.log(" tools <cmd> Tool discovery (list, search, info)"),console.log(" <domain> <tool> Execute a tool (e.g., navigation go-to --url ...)"),console.log(`
170
- Examples:`),console.log(" # Daemon & Session"),console.log(" daemon status"),console.log(" daemon info"),console.log(" session list"),console.log(" session delete my-session"),console.log(""),console.log(" # Tool Discovery"),console.log(" tools list"),console.log(" tools search screenshot"),console.log(" tools info navigation_go-to"),console.log(""),console.log(" # Navigation"),console.log(' navigation go-to --url "https://example.com"'),console.log(" navigation go-back"),console.log(" navigation reload"),console.log(""),console.log(" # Content"),console.log(' content take-screenshot --name "test"'),console.log(" content get-as-text"),console.log(' content get-as-html --selector "#main"'),console.log(""),console.log(" # Interaction"),console.log(' interaction click --ref "Submit"'),console.log(' interaction fill --ref "Email" --value "test@example.com"'),console.log(' interaction hover --ref "Menu"'),console.log(""),console.log(" # Accessibility"),console.log(" a11y get-snapshot"),console.log(" a11y get-ax-tree-snapshot"),console.log(`
171
- Tip: Use global options when starting interactive mode:`),console.log(" browser-devtools-cli --no-headless interactive"),console.log(" browser-devtools-cli --persistent --no-headless interactive"),console.log(),r.prompt();return}try{let n=D(c);await t.parseAsync(["node","repl",...n])}catch(n){n.code==="commander.help"||(n.code==="commander.unknownCommand"?(console.log(`Unknown command: ${c}`),console.log('Type "help" for available commands')):n.code==="commander.missingArgument"?console.error(`Missing argument: ${n.message}`):n.code==="commander.invalidArgument"?console.error(`Invalid argument: ${n.message}`):n.code&&n.code.startsWith("commander.")||console.error(`Error: ${n.message}`))}r.prompt()}),r.on("close",()=>{process.exit(0)})});o.addCommand(f);let A=new R("update").description("Check for updates and optionally install them").option("--check","Only check for updates without installing").action(async e=>{let t=o.opts(),r=W("../package.json").version,l="browser-devtools-mcp";console.log(`Checking for updates...
172
- `);try{let c=await fetch(`https://registry.npmjs.org/${l}/latest`,{method:"GET",signal:AbortSignal.timeout(1e4)});if(!c.ok)throw new Error(`Failed to check npm registry: HTTP ${c.status}`);let i=(await c.json()).version;if(t.json){b({currentVersion:r,latestVersion:i,updateAvailable:i!==r},!0);return}if(console.log(` Current version: ${r}`),console.log(` Latest version: ${i}`),console.log(),i===r){console.log("\x1B[32m\u2713 You are using the latest version!\x1B[0m");return}let u=r.split(".").map(Number),y=i.split(".").map(Number),S=!1;for(let C=0;C<3;C++)if(y[C]>u[C]){S=!0;break}else if(y[C]<u[C])break;if(!S){console.log("\x1B[32m\u2713 You are using a newer version than published!\x1B[0m");return}if(console.log(`\x1B[33m\u26A0 Update available: ${r} \u2192 ${i}\x1B[0m
173
- `),e.check){console.log("To update, run:"),console.log(` npm install -g ${l}@latest`),console.log("or"),console.log(` npx ${l}@latest`);return}let N=K.createInterface({input:process.stdin,output:process.stdout}),j=await new Promise(C=>{N.question("Do you want to update now? (y/N) ",_o=>{N.close(),C(_o.toLowerCase())})});if(j!=="y"&&j!=="yes"){console.log(`
174
- Update cancelled.`);return}console.log(`
175
- Updating...
176
- `);try{No(`npm install -g ${l}@latest`,{stdio:"inherit"}),console.log(`
177
- \x1B[32m\u2713 Update complete!\x1B[0m`),console.log("Please restart your terminal or run a new command to use the updated version.")}catch{console.error(`
178
- \x1B[31m\u2717 Update failed.\x1B[0m`),console.error("Try running manually with sudo:"),console.error(` sudo npm install -g ${l}@latest`),process.exit(1)}}catch(c){t.json?b({error:c.message,currentVersion:r},!0,!0):(console.error(`\x1B[31m\u2717 Failed to check for updates: ${c.message}\x1B[0m`),console.error(`
179
- You can manually check at:`),console.error(` https://www.npmjs.com/package/${l}`)),process.exit(1)}});o.addCommand(A),Y(o,T,async(e,t,r)=>{let l=r;try{await I(l);let c=await bo(l.port,e,t,l.sessionId,l.timeout);c.toolError&&(l.json?b({error:c.toolError},!0,!0):console.error(`Error: ${c.toolError.message||"Unknown error"}`),process.exit(1)),c.toolOutput&&(l.json?b(c.toolOutput,!0):console.log(x(c.toolOutput)))}catch(c){l.json?b({error:c.message},!0,!0):console.error(`Error: ${c.message}`),process.exit(1)}}),await o.parseAsync(process.argv)}p(Mo,"main");Mo().catch(o=>{console.error(`Fatal error: ${o.message}`),process.exit(1)});