@agimon-ai/browse-tool 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,8 +61,77 @@ Options:
61
61
  --headless Run browsers in headless mode
62
62
  --no-headless Run browsers in headed mode (visible window)
63
63
  -p, --profile <name> Default profile name for browser sessions
64
+ --custom-tools <path> Expose tools from a folder containing tools.yaml
64
65
  ```
65
66
 
67
+ ## Custom Tools
68
+
69
+ Custom tools are loaded from a folder containing:
70
+
71
+ - `tools.yaml`
72
+ - one or more `.ts` scripts referenced by the manifest
73
+
74
+ `tools.yaml` entries support:
75
+
76
+ - `name`
77
+ - `description`
78
+ - `script`
79
+ - `capabilities`
80
+ - `inputSchema`
81
+ - optional `suggestionActions`
82
+ - a short text string describing what should be done next after the tool succeeds
83
+ - this value is exposed in custom-tool discovery and injected into the tool execution result
84
+
85
+ If you start the MCP server with `--custom-tools <path>`, those tools are added to the MCP tool list automatically.
86
+
87
+ ### Expose Custom Tools Through MCP
88
+
89
+ ```bash
90
+ browse-tool mcp-serve --custom-tools scripts/automation/linkedin-prospecting
91
+ ```
92
+
93
+ Claude Code example:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "browse-tool": {
99
+ "command": "npx",
100
+ "args": [
101
+ "browse-tool",
102
+ "mcp-serve",
103
+ "--custom-tools",
104
+ "scripts/automation/linkedin-prospecting"
105
+ ]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Flat CLI Commands
112
+
113
+ List tools from a custom-tool directory:
114
+
115
+ ```bash
116
+ browse-tool list-custom-tools scripts/automation/linkedin-prospecting
117
+ ```
118
+
119
+ Execute a custom tool:
120
+
121
+ ```bash
122
+ browse-tool exec-custom-tool \
123
+ scripts/automation/linkedin-prospecting \
124
+ list_posts_from_linkedin_feed \
125
+ '{"pageId":"page-2","maxPosts":3}'
126
+ ```
127
+
128
+ ### Notes
129
+
130
+ - The flat commands talk to the browse-tool HTTP server.
131
+ - Custom tools are not exposed to MCP unless `mcp-serve` is started with `--custom-tools`.
132
+ - For slow UI-driven tools, prefer bounded inputs such as `maxPosts` so test runs stay manageable.
133
+ - If a manifest defines `suggestionActions`, browse-tool injects that text into the custom tool execution result.
134
+
66
135
  ## Tool Reference
67
136
 
68
137
  ### Input Tools (8)
package/dist/cli.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./stdio-ynNFGBY4.cjs`);require(`./playwright-test-CnsuVfC9.cjs`),require(`reflect-metadata/lite`);let t=require(`commander`),n=require(`@hono/node-server`),r=require(`@modelcontextprotocol/sdk/server/index.js`),i=require(`@modelcontextprotocol/sdk/server/stdio.js`),a=require(`@modelcontextprotocol/sdk/types.js`),o=require(`hono`),s=require(`hono/cors`),c=require(`inversify`),l=require(`node:fs`),u=require(`node:os`),d=require(`node:path`);d=e.m(d);let f=require(`zod`),p=require(`@agimon-ai/foundation-port-registry`),m=require(`@hono/node-ws`),h=require(`hono/html`),g=require(`hono/jsx/jsx-runtime`);var _=`0.2.0`;function v(t){let n=new o.Hono;return n.get(`/tasks`,n=>{try{let r=t.get(e.p.ExtensionTaskQueue).getNextTask();return r?n.json({task:{id:r.id,tool:r.tool,arguments:r.arguments}}):n.json({})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/result`,async n=>{try{let r=await n.req.json();if(!r.taskId)return n.json({success:!1,error:`Missing taskId in request body`},400);let i=t.get(e.p.ExtensionTaskQueue),a={taskId:r.taskId,success:r.success,result:r.result,error:r.error};return i.submitResult(a)?n.json({success:!0}):n.json({success:!1,error:`Task ${r.taskId} not found or already completed`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/status`,n=>{try{let r=t.get(e.p.ExtensionTaskQueue),i=r.getConnectionStatus(),a={connected:i.connected,lastPollAt:i.lastPollAt?.toISOString(),lastResultAt:i.lastResultAt?.toISOString(),pendingTasks:i.pendingTasks,queueSize:r.queueSize};return n.json(a)}catch(e){return n.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/register`,async n=>{try{let r=await n.req.json();if(!r.browserId)return n.json({success:!1,error:`Missing browserId in request body`},400);let i=t.get(e.p.ExtensionSessionRegistry).register(r);return n.json({success:!0,session:{id:i.id,browserId:i.browserId,controlMode:i.controlMode,createdAt:i.createdAt.toISOString()}})}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/heartbeat`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.p.ExtensionSessionRegistry).heartbeat(r);return i?n.json({success:!0,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested,lastHeartbeatAt:i.lastHeartbeatAt.toISOString()}}):n.json({success:!1,error:`Session ${r.sessionId} not found`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/handoff`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.p.ExtensionSessionRegistry).requestHandoff(r);return i?n.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested}}):n.json({success:!1,error:`Session ${r.sessionId} not found`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/handoff/acknowledge`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.p.ExtensionSessionRegistry).acknowledgeHandoff(r.sessionId);return i?n.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested}}):n.json({success:!1,error:`Session ${r.sessionId} not found or no handoff pending`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/sessions`,n=>{try{let r=t.get(e.p.ExtensionSessionRegistry).listSessions();return n.json({sessions:r.map(e=>({id:e.id,browserId:e.browserId,tabId:e.tabId,currentUrl:e.currentUrl,controlMode:e.controlMode,activeSpecPath:e.activeSpecPath,handoffRequested:e.handoffRequested,createdAt:e.createdAt.toISOString(),lastHeartbeatAt:e.lastHeartbeatAt.toISOString()}))})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n}function y(){return new c.ContainerModule(t=>{t.bind(e.p.ExtensionTaskQueue).to(e.f).inSingletonScope(),t.bind(e.p.ExtensionToolDelegator).to(e.d).inSingletonScope()})}function b(e){let t=new r.Server({name:`browse-tool-chrome`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getSupportedTools().map(e=>({name:e,description:`Browser automation tool (Chrome extension mode): ${e}`,inputSchema:{type:`object`,properties:{},additionalProperties:!0}}));return t.setRequestHandler(a.ListToolsRequestSchema,async()=>({tools:n})),t.setRequestHandler(a.CallToolRequestSchema,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const x=new t.Command(`chrome-serve`).description(`[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation`).option(`-p, --port <port>`,`HTTP server port for extension polling`,`3200`).option(`-v, --verbose`,`Enable verbose output`,!1).option(`--wait-for-extension`,`Wait for extension to connect before accepting MCP requests`,!1).action(async t=>{console.error(``),console.error(`╔════════════════════════════════════════════════════════════════╗`),console.error(`║ DEPRECATED: chrome-serve is deprecated. ║`),console.error(`║ Use mcp-serve instead - extension routes are now ║`),console.error(`║ automatically available in the HTTP server at /extension/*. ║`),console.error(`╚════════════════════════════════════════════════════════════════╝`),console.error(``);try{let r=typeof t.port==`string`?Number.parseInt(t.port,10):t.port;t.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${r}`),console.error(` Wait for extension: ${t.waitForExtension}`));let a=new c.Container({defaultScope:`Singleton`});a.load(y());let l=a.get(e.p.ExtensionTaskQueue),u=a.get(e.p.ExtensionToolDelegator),d=new o.Hono;d.use(`*`,(0,s.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let f=v(a);d.route(`/extension`,f),d.get(`/health`,e=>{let t=l.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let p=(0,n.serve)({fetch:d.fetch,port:r});p.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${r} is already in use.`),console.error(`Recovery: Try a different port with --port <port>`)):console.error(`Error [SERVER_ERROR]: HTTP server error: ${e.message}`),process.exit(1)}),console.error(`HTTP server started on port ${r}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${r}/extension/tasks`),t.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{l.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let m=b(u),h=new i.StdioServerTransport;await m.connect(h),console.error(`Chrome extension MCP server started on stdio`);let g=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{l.clearAllTasks(`Server shutting down`),await h.close(),p.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>g(`SIGINT`)),process.on(`SIGTERM`,()=>g(`SIGTERM`))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${t}`),console.error(`Recovery: Check that the port is available and try again.`),process.exit(1)}}),S=`BROWSE_TOOL_CONFIG`,C=`.browse-tool`,w=`config.json`,T=[`json`,`text`,`quiet`],E={commands:{},tools:{}},ee=f.z.record(f.z.string(),f.z.unknown()),te=f.z.object({mcpServe:f.z.object({type:f.z.string().optional(),browser:f.z.string().optional(),headless:f.z.boolean().optional(),profile:f.z.string().optional(),mode:f.z.string().optional(),host:f.z.string().optional(),tags:f.z.string().optional(),exclude:f.z.string().optional(),registryPath:f.z.string().optional(),registryDir:f.z.string().optional(),pidsDir:f.z.string().optional(),profilesDir:f.z.string().optional()}).partial().optional(),httpServe:f.z.object({port:f.z.coerce.number().int().positive().optional(),headless:f.z.boolean().optional(),idleTimeout:f.z.coerce.number().positive().optional(),host:f.z.string().optional(),registryDir:f.z.string().optional(),registryPath:f.z.string().optional(),pidsDir:f.z.string().optional(),profilesDir:f.z.string().optional()}).partial().optional(),exec:f.z.object({format:f.z.enum(T).optional(),color:f.z.boolean().optional(),port:f.z.coerce.number().int().positive().optional()}).partial().optional(),tools:f.z.object({format:f.z.enum(T).optional(),color:f.z.boolean().optional(),port:f.z.coerce.number().int().positive().optional()}).partial().optional(),status:f.z.record(f.z.string(),f.z.unknown()).optional(),stop:f.z.record(f.z.string(),f.z.unknown()).optional()}),ne=f.z.object({commands:te.default({}),tools:f.z.record(f.z.string(),ee).default({})});let D={config:E};function re(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(n===`--config`)return e[t+1];if(n.startsWith(`--config=`))return n.slice(9)}}function ie(e){let t=d.default.resolve(e);if(!(0,l.existsSync)(t))throw Error(`Config path not found: ${t}`);return(0,l.statSync)(t).isDirectory()?d.default.join(t,w):t}function ae(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??(0,u.homedir)(),a=re(e);if(a)return ie(a);if(n[S])return ie(n[S]);let o=d.default.join(r,C,w);if((0,l.existsSync)(o))return o;let s=d.default.join(i,C,w);if((0,l.existsSync)(s))return s}function oe(e){let t=(0,l.readFileSync)(e,`utf8`),n=JSON.parse(t);return ne.parse(n)}function se(e,t={}){let n=ae(e,t);return n?{configPath:n,config:oe(n)}:{config:E}}function ce(e,t={}){return D=se(e,t),D}function O(e){return(D.config.commands??{})[e]??{}}function le(e){return D.config.tools?.[e]??{}}function k(e,t,n,r,i){let a=e.getOptionValueSourceWithGlobals(t);return a!==void 0&&a!==`default`&&a!==`implied`?n:i===void 0?r===void 0?n:r:i}const ue=6e4;var de=class{port;timeout;serverPort=null;constructor(e={}){this.port=e.port??3200,this.timeout=e.timeout??6e4}async ensureServer(){if(this.serverPort!==null)return this.serverPort;let t=await e.a().get(e.p.HttpServerManager).ensureRunning(this.port);if(!t.running||!t.port)throw Error(t.error??`Failed to start HTTP server. Try running "browse-tool http-serve" manually.`);return this.serverPort=t.port,t.port}async getBaseUrl(){return`http://localhost:${await this.ensureServer()}`}async listTools(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/tools`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);return(await n.json()).tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async listBrowsers(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/browsers`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.browsers}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async execute(e,t){let n=await this.getBaseUrl(),r=e===`run_spec`?void 0:this.timeout,i=new AbortController,a=r===void 0?void 0:setTimeout(()=>i.abort(),r);try{let a=await fetch(`${n}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:e,arguments:t}),signal:r===void 0?void 0:i.signal});if(!a.ok){let e=await a.text();throw Error(`HTTP ${a.status}: ${e||a.statusText}`)}let o=await a.json();return o.success?o.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:o.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`):this.wrapConnectionError(e)}finally{a&&clearTimeout(a)}}wrapConnectionError(e){return e instanceof Error?e.message.includes(`ECONNREFUSED`)||e.message.includes(`fetch failed`)?Error(`Cannot connect to browse-tool HTTP server.\n\nTry one of the following:\n 1. Start the server: browse-tool http-serve\n 2. Check if another process is using port ${this.port}\n 3. Run with a different port: browse-tool --port 3201 <command>\n\nOriginal error: ${e.message}`):e:Error(String(e))}};function A(e){return new de(e)}const j={reset:`\x1B[0m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`,gray:`\x1B[90m`,bold:`\x1B[1m`};function M(e,t,n){return n?`${j[t]}${e}${j.reset}`:e}function fe(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?pe(e):me(e,r)}function pe(e){return JSON.stringify(e,null,2)}function me(e,t){let n=[];e.isError&&n.push(M(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(he(r,t)):r.type===`image`?n.push(ve(r,t)):n.push(M(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
3
- `)}function he(e,t){let n=e.text;try{return ge(JSON.parse(n),t)}catch{return n}}function ge(e,t){if(typeof e!=`object`||!e)return String(e);let n=[];for(let[r,i]of Object.entries(e)){let e=M(r,`cyan`,t),a=_e(i,t);n.push(`${e}: ${a}`)}return n.join(`
4
- `)}function _e(e,t){return e===null?M(`null`,`gray`,t):e===void 0?M(`undefined`,`gray`,t):typeof e==`boolean`?M(String(e),e?`green`:`red`,t):typeof e==`number`?M(String(e),`yellow`,t):typeof e==`string`?e.startsWith(`http://`)||e.startsWith(`https://`)?M(e,`blue`,t):e:Array.isArray(e)?e.length===0?M(`[]`,`gray`,t):JSON.stringify(e,null,2):typeof e==`object`?JSON.stringify(e,null,2):String(e)}function ve(e,t){let{mimeType:n,data:r}=e;return M(`[Image: ${n}, ~${Math.round(r.length*3/4/1024)}KB base64 data]`,`magenta`,t)}function N(e,t){return M(`Error: ${e instanceof Error?e.message:e}`,`red`,t)}const ye=new t.Command(`exec`).description(`Execute a tool directly with JSON arguments`).argument(`<tool>`,`Tool name to execute (e.g., browser_launch)`).argument(`[args]`,`JSON arguments for the tool`,`{}`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s)).action(async function(e,t,n){let r=O(`exec`),i=k(this,`format`,n.format,r.format),a=k(this,`color`,n.color,r.color),o=k(this,`port`,n.port,r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),s={format:i,color:a};try{let n;try{n=JSON.parse(t)}catch{console.error(N(`Invalid JSON arguments: ${t}`,s.color)),process.exit(1)}let r=await A({port:Number.parseInt(o,10)}).execute(e,n),i=fe(r,s);i&&console.log(i),r.isError&&process.exit(1)}catch(e){console.error(N(e instanceof Error?e:String(e),s.color)),process.exit(1)}});function be(t){let n=new o.Hono;return n.get(`/browsers`,async n=>{try{let r=t.get(e.p.BrowserService),i=t.get(e.p.PageRegistry),a=r.listBrowsers(),o=i.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString(),pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt.toISOString()}))}}),c={totalBrowsers:a.length,totalPages:o.length};return n.json({browsers:s,stats:c})}catch(e){return console.error(`Failed to list browsers:`,e),n.json({error:`Failed to list browsers`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/browsers`,async n=>{try{return await t.get(e.p.BrowserService).closeAll(),n.json({success:!0,message:`All browsers closed`})}catch(e){return console.error(`Failed to close all browsers:`,e),n.json({error:`Failed to close all browsers`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/browsers/:id`,async n=>{try{let r=n.req.param(`id`),i=t.get(e.p.BrowserService);return i.getBrowser(r)?(await i.closeBrowser(r),n.json({success:!0,message:`Browser "${r}" closed`})):n.json({error:`Browser "${r}" not found`},404)}catch(e){return console.error(`Failed to close browser:`,e),n.json({error:`Failed to close browser`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/pages/:id`,async n=>{try{let r=n.req.param(`id`),i=t.get(e.p.PageRegistry).get(r);return i?i.page?(await i.page.close(),n.json({success:!0,message:`Page "${r}" closed`})):n.json({error:`Page "${r}" is in extension mode and cannot be closed via API`},400):n.json({error:`Page "${r}" not found`},404)}catch(e){return console.error(`Failed to close page:`,e),n.json({error:`Failed to close page`,message:e instanceof Error?e.message:String(e)},500)}}),n}function P(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}function xe(e){let t=Math.floor(e/1e3),n=Math.floor(t/60),r=Math.floor(n/60);return r>0?`${r}h ${n%60}m`:n>0?`${n}m ${t%60}s`:`${t}s`}function F(e){return xe(new Date().getTime()-e.getTime())}const Se=`dashboard-container`,Ce=`dashboard-header`,we=`btn`,Te=`btn-danger`,I=`table-container`,Ee=`browser-table`,De=`stats-container`,L=`stat-card`,R=`browser-row`,z=`page-row`,B=`status-active`,V=`url-cell`,H=`timestamp-cell`,U=`actions-cell`,W=`kill-btn`,Oe=`refresh-btn`,G=`empty-state`,ke=`
2
+ const e=require(`./stdio-DKou0TqY.cjs`);require(`./playwright-test-DTw_9rvK.cjs`),require(`reflect-metadata/lite`);let t=require(`commander`),n=require(`@hono/node-server`),r=require(`@modelcontextprotocol/sdk/server/index.js`),i=require(`@modelcontextprotocol/sdk/server/stdio.js`),a=require(`@modelcontextprotocol/sdk/types.js`),o=require(`hono`),s=require(`hono/cors`),c=require(`inversify`),l=require(`node:fs`),u=require(`node:path`);u=e._(u);let d=require(`@opentelemetry/api`),f=require(`@agimon-ai/foundation-port-registry`),p=require(`node:os`),m=require(`zod`),h=require(`node:fs/promises`),g=require(`node:module`),_=require(`@hono/node-ws`),v=require(`hono/html`),y=require(`hono/jsx/jsx-runtime`);var b=`0.2.1`;function x(t){let n=new o.Hono;return n.get(`/telemetry-config`,t=>{try{let n=new URL(t.req.url).origin;return t.json(e.h(process.env,n))}catch(e){return t.json({enabled:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/tasks`,n=>{try{let r=t.get(e.g.ExtensionTaskQueue).getNextTask();return r?n.json({task:{id:r.id,tool:r.tool,arguments:r.arguments,telemetry:r.telemetry}}):n.json({})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/result`,async n=>{try{let r=await n.req.json();if(!r.taskId)return n.json({success:!1,error:`Missing taskId in request body`},400);let i=t.get(e.g.ExtensionTaskQueue),a={taskId:r.taskId,success:r.success,result:r.result,error:r.error};return i.submitResult(a)?n.json({success:!0}):n.json({success:!1,error:`Task ${r.taskId} not found or already completed`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/status`,n=>{try{let r=t.get(e.g.ExtensionTaskQueue),i=r.getConnectionStatus(),a={connected:i.connected,lastPollAt:i.lastPollAt?.toISOString(),lastResultAt:i.lastResultAt?.toISOString(),pendingTasks:i.pendingTasks,queueSize:r.queueSize};return n.json(a)}catch(e){return n.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/register`,async n=>{try{let r=await n.req.json();if(!r.browserId)return n.json({success:!1,error:`Missing browserId in request body`},400);let i=t.get(e.g.ExtensionSessionRegistry).register(r);return n.json({success:!0,session:{id:i.id,browserId:i.browserId,controlMode:i.controlMode,createdAt:i.createdAt.toISOString()}})}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/heartbeat`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.g.ExtensionSessionRegistry).heartbeat(r);return i?n.json({success:!0,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested,lastHeartbeatAt:i.lastHeartbeatAt.toISOString()}}):n.json({success:!1,error:`Session ${r.sessionId} not found`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/handoff`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.g.ExtensionSessionRegistry).requestHandoff(r);return i?n.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested}}):n.json({success:!1,error:`Session ${r.sessionId} not found`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.post(`/handoff/acknowledge`,async n=>{try{let r=await n.req.json();if(!r.sessionId)return n.json({success:!1,error:`Missing sessionId in request body`},400);let i=t.get(e.g.ExtensionSessionRegistry).acknowledgeHandoff(r.sessionId);return i?n.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:i.id,controlMode:i.controlMode,handoffRequested:i.handoffRequested}}):n.json({success:!1,error:`Session ${r.sessionId} not found or no handoff pending`},404)}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/sessions`,n=>{try{let r=t.get(e.g.ExtensionSessionRegistry).listSessions();return n.json({sessions:r.map(e=>({id:e.id,browserId:e.browserId,tabId:e.tabId,currentUrl:e.currentUrl,controlMode:e.controlMode,activeSpecPath:e.activeSpecPath,handoffRequested:e.handoffRequested,createdAt:e.createdAt.toISOString(),lastHeartbeatAt:e.lastHeartbeatAt.toISOString()}))})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n}function S(){return new c.ContainerModule(t=>{t.bind(e.g.ExtensionTaskQueue).to(e.p).inSingletonScope(),t.bind(e.g.ExtensionToolDelegator).to(e.f).inSingletonScope()})}function C(e){let t=new r.Server({name:`browse-tool-chrome`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getSupportedTools().map(e=>({name:e,description:`Browser automation tool (Chrome extension mode): ${e}`,inputSchema:{type:`object`,properties:{},additionalProperties:!0}}));return t.setRequestHandler(a.ListToolsRequestSchema,async()=>({tools:n})),t.setRequestHandler(a.CallToolRequestSchema,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const w=new t.Command(`chrome-serve`).description(`[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation`).option(`-p, --port <port>`,`HTTP server port for extension polling`,`3200`).option(`-v, --verbose`,`Enable verbose output`,!1).option(`--wait-for-extension`,`Wait for extension to connect before accepting MCP requests`,!1).action(async t=>{console.error(``),console.error(`╔════════════════════════════════════════════════════════════════╗`),console.error(`║ DEPRECATED: chrome-serve is deprecated. ║`),console.error(`║ Use mcp-serve instead - extension routes are now ║`),console.error(`║ automatically available in the HTTP server at /extension/*. ║`),console.error(`╚════════════════════════════════════════════════════════════════╝`),console.error(``);try{let r=typeof t.port==`string`?Number.parseInt(t.port,10):t.port;t.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${r}`),console.error(` Wait for extension: ${t.waitForExtension}`));let a=new c.Container({defaultScope:`Singleton`});a.load(S());let l=a.get(e.g.ExtensionTaskQueue),u=a.get(e.g.ExtensionToolDelegator),d=new o.Hono;d.use(`*`,(0,s.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let f=x(a);d.route(`/extension`,f),d.get(`/health`,e=>{let t=l.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let p=(0,n.serve)({fetch:d.fetch,port:r});p.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${r} is already in use.`),console.error(`Recovery: Try a different port with --port <port>`)):console.error(`Error [SERVER_ERROR]: HTTP server error: ${e.message}`),process.exit(1)}),console.error(`HTTP server started on port ${r}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${r}/extension/tasks`),t.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{l.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let m=C(u),h=new i.StdioServerTransport;await m.connect(h),console.error(`Chrome extension MCP server started on stdio`);let g=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{l.clearAllTasks(`Server shutting down`),await h.close(),p.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>g(`SIGINT`)),process.on(`SIGTERM`,()=>g(`SIGTERM`))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${t}`),console.error(`Recovery: Check that the port is available and try again.`),process.exit(1)}}),ee=`BROWSE_TOOL_CONFIG`,te=`.browse-tool`,T=`config.json`,ne=[`json`,`text`,`quiet`],re={commands:{},tools:{}},ie=m.z.record(m.z.string(),m.z.unknown()),ae=m.z.object({mcpServe:m.z.object({type:m.z.string().optional(),browser:m.z.string().optional(),headless:m.z.boolean().optional(),profile:m.z.string().optional(),mode:m.z.string().optional(),host:m.z.string().optional(),tags:m.z.string().optional(),exclude:m.z.string().optional(),customTools:m.z.string().optional(),registryPath:m.z.string().optional(),registryDir:m.z.string().optional(),pidsDir:m.z.string().optional(),profilesDir:m.z.string().optional()}).partial().optional(),httpServe:m.z.object({port:m.z.coerce.number().int().positive().optional(),headless:m.z.boolean().optional(),idleTimeout:m.z.coerce.number().positive().optional(),host:m.z.string().optional(),registryDir:m.z.string().optional(),registryPath:m.z.string().optional(),pidsDir:m.z.string().optional(),profilesDir:m.z.string().optional()}).partial().optional(),exec:m.z.object({format:m.z.enum(ne).optional(),color:m.z.boolean().optional(),port:m.z.coerce.number().int().positive().optional()}).partial().optional(),tools:m.z.object({format:m.z.enum(ne).optional(),color:m.z.boolean().optional(),port:m.z.coerce.number().int().positive().optional()}).partial().optional(),status:m.z.record(m.z.string(),m.z.unknown()).optional(),stop:m.z.record(m.z.string(),m.z.unknown()).optional()}),oe=m.z.object({commands:ae.default({}),tools:m.z.record(m.z.string(),ie).default({})});let E={config:re};function se(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(n===`--config`)return e[t+1];if(n.startsWith(`--config=`))return n.slice(9)}}function ce(e){let t=u.default.resolve(e);if(!(0,l.existsSync)(t))throw Error(`Config path not found: ${t}`);return(0,l.statSync)(t).isDirectory()?u.default.join(t,T):t}function le(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??(0,p.homedir)(),a=se(e);if(a)return ce(a);if(n[ee])return ce(n[ee]);let o=u.default.join(r,te,T);if((0,l.existsSync)(o))return o;let s=u.default.join(i,te,T);if((0,l.existsSync)(s))return s}function ue(e){let t=(0,l.readFileSync)(e,`utf8`),n=JSON.parse(t);return oe.parse(n)}function de(e,t={}){let n=le(e,t);return n?{configPath:n,config:ue(n)}:{config:re}}function fe(e,t={}){return E=de(e,t),E}function D(e){return(E.config.commands??{})[e]??{}}function pe(e){return E.config.tools?.[e]??{}}function O(e,t,n,r,i){let a=e.getOptionValueSourceWithGlobals(t);return a!==void 0&&a!==`default`&&a!==`implied`?n:i===void 0?r===void 0?n:r:i}const me=6e4;var he=class{port;timeout;serverPort=null;constructor(e={}){this.port=e.port??3200,this.timeout=e.timeout??6e4}async ensureServer(){if(this.serverPort!==null)return this.serverPort;let t=await e.a().get(e.g.HttpServerManager).ensureRunning(this.port);if(!t.running||!t.port)throw Error(t.error??`Failed to start HTTP server. Try running "browse-tool http-serve" manually.`);return this.serverPort=t.port,t.port}async getBaseUrl(){return`http://localhost:${await this.ensureServer()}`}async listTools(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/tools`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async listCustomTools(e){let t=await this.getBaseUrl(),n=new AbortController,r=setTimeout(()=>n.abort(),this.timeout);try{let r=new URL(`/custom-tools`,t);r.searchParams.set(`dir`,e);let i=await fetch(r,{method:`GET`,headers:{Accept:`application/json`},signal:n.signal});if(!i.ok)throw Error(`HTTP ${i.status}: ${i.statusText}`);let a=await i.json();if(a.error)throw Error(a.error);return a.tools}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(r)}}async listBrowsers(){let e=await this.getBaseUrl(),t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{let n=await fetch(`${e}/browsers`,{method:`GET`,headers:{Accept:`application/json`},signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);return r.browsers}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(n)}}async execute(e,t){let n=await this.getBaseUrl(),r=e===`run_spec`?void 0:this.timeout,i=new AbortController,a=r===void 0?void 0:setTimeout(()=>i.abort(),r);try{let a=await fetch(`${n}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:e,arguments:t}),signal:r===void 0?void 0:i.signal});if(!a.ok){let e=await a.text();throw Error(`HTTP ${a.status}: ${e||a.statusText}`)}let o=await a.json();return o.success?o.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:o.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{a&&clearTimeout(a)}}async executeCustomTool(e,t,n){let r=await this.getBaseUrl(),i=new AbortController,a=setTimeout(()=>i.abort(),this.timeout);try{let a=new URL(`/custom-tools`,r);a.searchParams.set(`dir`,e);let o=await fetch(a,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify({tool:t,arguments:n}),signal:i.signal});if(!o.ok){let e=await o.text();throw Error(`HTTP ${o.status}: ${e||o.statusText}`)}let s=await o.json();return s.success?s.result??{content:[{type:`text`,text:`No result returned`}]}:{content:[{type:`text`,text:s.error??`Unknown error`}],isError:!0}}catch(e){throw e instanceof Error&&e.name===`AbortError`?Error(`Request timeout after ${this.timeout}ms`,{cause:e}):this.wrapConnectionError(e)}finally{clearTimeout(a)}}wrapConnectionError(e){return e instanceof Error?e.message.includes(`ECONNREFUSED`)||e.message.includes(`fetch failed`)?Error(`Cannot connect to browse-tool HTTP server.\n\nTry one of the following:\n 1. Start the server: browse-tool http-serve\n 2. Check if another process is using port ${this.port}\n 3. Run with a different port: browse-tool --port 3201 <command>\n\nOriginal error: ${e.message}`,{cause:e}):e:Error(String(e),{cause:e})}};function k(e){return new he(e)}const ge={reset:`\x1B[0m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`,gray:`\x1B[90m`,bold:`\x1B[1m`};function A(e,t,n){return n?`${ge[t]}${e}${ge.reset}`:e}function j(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?_e(e):ve(e,r)}function _e(e){return JSON.stringify(e,null,2)}function ve(e,t){let n=[];e.isError&&n.push(A(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(ye(r,t)):r.type===`image`?n.push(Se(r,t)):n.push(A(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
3
+ `)}function ye(e,t){let n=e.text;try{return be(JSON.parse(n),t)}catch{return n}}function be(e,t){if(typeof e!=`object`||!e)return String(e);let n=[];for(let[r,i]of Object.entries(e)){let e=A(r,`cyan`,t),a=xe(i,t);n.push(`${e}: ${a}`)}return n.join(`
4
+ `)}function xe(e,t){return e===null?A(`null`,`gray`,t):e===void 0?A(`undefined`,`gray`,t):typeof e==`boolean`?A(String(e),e?`green`:`red`,t):typeof e==`number`?A(String(e),`yellow`,t):typeof e==`string`?e.startsWith(`http://`)||e.startsWith(`https://`)?A(e,`blue`,t):e:Array.isArray(e)?e.length===0?A(`[]`,`gray`,t):JSON.stringify(e,null,2):typeof e==`object`?JSON.stringify(e,null,2):String(e)}function Se(e,t){let{mimeType:n,data:r}=e;return A(`[Image: ${n}, ~${Math.round(r.length*3/4/1024)}KB base64 data]`,`magenta`,t)}function M(e,t){return A(`Error: ${e instanceof Error?e.message:e}`,`red`,t)}function Ce(e,t){let n=Math.max(...e.map(e=>e.name.length)),r=[];for(let i of e){let e=A(i.name.padEnd(n),`cyan`,t);r.push(` ${e} ${i.description}`)}return r.join(`
5
+ `)}function N(e,t){let n=D(`tools`),r=O(e,`format`,t.format,n.format),i=O(e,`color`,t.color,n.color);return{port:O(e,`port`,t.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),formatterOptions:{format:r,color:i}}}const P=new t.Command(`list-custom-tools`).description(`List custom tools from a tools directory`).argument(`<dir>`,`Path to the tools directory containing tools.yaml`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s)).action(async function(e,t){let{port:n,formatterOptions:r}=N(this,t);try{let t=await k({port:Number.parseInt(n,10)}).listCustomTools(e),i=r.format===`text`?Ce(t,r.color):r.format===`quiet`?``:JSON.stringify(t,null,2);i&&console.log(i)}catch(e){console.error(M(e instanceof Error?e:String(e),r.color)),process.exit(1)}}),F=new t.Command(`exec-custom-tool`).description(`Execute a custom tool from a tools directory`).argument(`<dir>`,`Path to the tools directory containing tools.yaml`).argument(`<tool>`,`Custom tool name to execute`).argument(`[args]`,`JSON arguments for the tool`,`{}`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s)).action(async function(e,t,n,r){let{port:i,formatterOptions:a}=N(this,r);try{let r;try{r=JSON.parse(n)}catch{console.error(M(`Invalid JSON arguments: ${n}`,a.color)),process.exit(1);return}let o=await k({port:Number.parseInt(i,10)}).executeCustomTool(e,t,r),s=j(o,a);s&&console.log(s),o.isError&&process.exit(1)}catch(e){console.error(M(e instanceof Error?e:String(e),a.color)),process.exit(1)}}),we=new t.Command(`custom-tools`).description(`List and execute custom tools through the HTTP server`).addCommand(P).addCommand(F),Te=new t.Command(`exec`).description(`Execute a tool directly with JSON arguments`).argument(`<tool>`,`Tool name to execute (e.g., browser_launch)`).argument(`[args]`,`JSON arguments for the tool`,`{}`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s)).action(async function(e,t,n){let r=D(`exec`),i=O(this,`format`,n.format,r.format),a=O(this,`color`,n.color,r.color),o=O(this,`port`,n.port,r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),s={format:i,color:a};try{let n;try{n=JSON.parse(t)}catch{console.error(M(`Invalid JSON arguments: ${t}`,s.color)),process.exit(1)}let r=await k({port:Number.parseInt(o,10)}).execute(e,n),i=j(r,s);i&&console.log(i),r.isError&&process.exit(1)}catch(e){console.error(M(e instanceof Error?e:String(e),s.color)),process.exit(1)}}),Ee=`tools.yaml`,I=`pageId`,De=m.z.record(m.z.string(),m.z.unknown()),Oe=m.z.object({type:m.z.literal(`object`),properties:m.z.record(m.z.string(),m.z.unknown()).optional(),required:m.z.array(m.z.string()).optional(),additionalProperties:m.z.boolean().optional()}).passthrough(),ke=m.z.object({name:m.z.string().min(1),description:m.z.string().min(1),script:m.z.string().min(1),suggestionActions:m.z.string().min(1).optional(),capabilities:De,inputSchema:Oe}),Ae=m.z.object({tools:m.z.array(ke)}),je=process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS===`1`;function Me(e){return JSON.stringify(e,(e,t)=>typeof t==`string`&&t.length>240?`${t.slice(0,240)}...<trimmed>`:t)}function L(e,t){if(je){if(t){console.error(`[CustomToolService] ${e}`,t);return}console.error(`[CustomToolService] ${e}`)}}function R(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ne(e){return R(e)&&Array.isArray(e.content)}function z(e){let t=e.trim();return t===``?``:t.startsWith(`"`)&&t.endsWith(`"`)||t.startsWith(`[`)&&t.endsWith(`]`)||t.startsWith(`{`)&&t.endsWith(`}`)?JSON.parse(t):t.startsWith(`'`)&&t.endsWith(`'`)?t.slice(1,-1):t===`true`?!0:t===`false`?!1:t===`null`?null:/^-?\d+(\.\d+)?$/.test(t)?Number(t):t}function Pe(e){if(!e||Object.keys(e).length===0)return;let t={};for(let[n,r]of Object.entries(e))if(r!=null){if(typeof r==`string`||typeof r==`number`||typeof r==`boolean`){t[n]=r;continue}t[n]=JSON.stringify(r)}return Object.keys(t).length>0?t:void 0}function Fe(e){return e.replace(/^\uFEFF/,``).split(/\r?\n/).map(e=>{if(e.includes(` `))throw Error(`Tab indentation is not supported in tools.yaml`);let t=e.replace(/\s+#.*$/,``);return t.trim().length===0?null:{indent:t.match(/^ */)?.[0].length??0,text:t.trim()}}).filter(e=>e!==null)}function B(e,t,n){let r=e[t];if(!r||r.indent!==n)throw Error(`Invalid indentation in tools.yaml at line ${t+1}`);return r.text.startsWith(`-`)?Ie(e,t,n):Le(e,t,n)}function Ie(e,t,n){let r=[],i=t;for(;i<e.length;){let t=e[i];if(t.indent<n||t.indent!==n||!t.text.startsWith(`-`))break;let a=t.text.slice(1).trim();if(a===``){let t=e[i+1];if(!t||t.indent<=n){r.push(null),i+=1;continue}let[a,o]=B(e,i+1,t.indent);r.push(a),i=o;continue}if(a.includes(`:`)){let[t,o]=V(a),s={};if(o===void 0){let r=e[i+1];if(!r||r.indent<=n)throw Error(`Expected nested value for "${t}" in tools.yaml`);let[a,o]=B(e,i+1,r.indent);s[t]=a,i=o}else s[t]=z(o),i+=1;for(;i<e.length&&e[i].indent>n;){let t=e[i];if(t.indent!==n+2||t.text.startsWith(`-`)){let[n,r]=B(e,i,t.indent);if(!R(n))throw Error(`Expected object entry in tools.yaml at line ${i+1}`);Object.assign(s,n),i=r;continue}let[r,a]=V(t.text);if(a===void 0){let n=e[i+1];if(!n||n.indent<=t.indent)throw Error(`Expected nested value for "${r}" in tools.yaml`);let[a,o]=B(e,i+1,n.indent);s[r]=a,i=o;continue}s[r]=z(a),i+=1}r.push(s);continue}r.push(z(a)),i+=1}return[r,i]}function Le(e,t,n){let r={},i=t;for(;i<e.length;){let t=e[i];if(t.indent<n||t.indent!==n||t.text.startsWith(`-`))break;let[a,o]=V(t.text);if(o===void 0){let t=e[i+1];if(!t||t.indent<=n){r[a]=null,i+=1;continue}let[o,s]=B(e,i+1,t.indent);r[a]=o,i=s;continue}r[a]=z(o),i+=1}return[r,i]}function V(e){let t=e.indexOf(`:`);if(t===-1)throw Error(`Invalid tools.yaml entry: "${e}"`);let n=e.slice(0,t).trim(),r=e.slice(t+1).trim();return[n,r===``?void 0:r]}function Re(e){let t=Fe(e);if(t.length===0)return{};let[n]=B(t,0,t[0].indent);return n}function ze(e){let t=e.inputSchema.properties,n=e.inputSchema.required??[];if(!t||!R(t))throw Error(`Custom tool "${e.name}" must define inputSchema.properties`);let r=t[I];if(!R(r)||r.type!==`string`)throw Error(`Custom tool "${e.name}" must define inputSchema.properties.pageId as type "string"`);if(!n.includes(I))throw Error(`Custom tool "${e.name}" must require pageId in inputSchema.required`)}async function Be(e){let t=(0,g.stripTypeScriptTypes)(await(0,h.readFile)(e,`utf8`),{mode:`strip`});return await import(`data:text/javascript;base64,${Buffer.from(t,`utf8`).toString(`base64`)}`)}function Ve(e,t){let n=typeof t.default==`function`?t.default:void 0;if(!n)throw Error(`Custom tool script "${e}" must export a default function`);return{execute:n}}function He(e,t){return typeof e.suggestionActions==`string`&&e.suggestionActions.trim().length>0?e:{...e,suggestionActions:t}}function Ue(e,t){let n=e.content[0];if(n?.type===`text`&&typeof n.text==`string`)try{let r=JSON.parse(n.text);if(R(r)){let i=He(r,t);return{...e,content:[{...n,text:JSON.stringify(i,null,2)},...e.content.slice(1)]}}}catch{}return{...e,content:[...e.content,{type:`text`,text:`suggestionActions: ${t}`}]}}function We(e,t,n){if(Ne(t))return n?Ue(t,n):t;if(typeof t==`string`)return n?{content:[{type:`text`,text:JSON.stringify({result:t,suggestionActions:n},null,2)}]}:{content:[{type:`text`,text:t}]};if(t===void 0)return n?{content:[{type:`text`,text:JSON.stringify({result:`Custom tool "${e}" completed successfully`,suggestionActions:n},null,2)}]}:{content:[{type:`text`,text:`Custom tool "${e}" completed successfully`}]};let r=R(t)&&n?He(t,n):t;return{content:[{type:`text`,text:JSON.stringify(r,null,2)}]}}var Ge=class{constructor(t,n,r=new e.m){this.pageRegistry=t,this.extensionTaskQueue=n,this.telemetry=r}resolveToolPage(t,n,r){if(r.page)return r.page;if(r.mode===`extension`&&this.extensionTaskQueue){let t=new e.d(this.extensionTaskQueue);return t.setTarget(n,r.browserId),t}throw Error(`Custom tool "${t}" requires a supported page context`)}createToolLogger(e,t,n){let r={"browse_tool.tool.name":e,"browse_tool.page.id":t,"browse_tool.browser.id":n.browserId,"browse_tool.execution.mode":n.mode},i=(e,t,n)=>{this.telemetry.log(e,t,{attributes:Pe({...r,...n?.attributes??{}}),exception:n?.exception})};return{getTraceContext:()=>this.telemetry.getActiveTraceContext(),trace:(e,t)=>i(`trace`,e,t),debug:(e,t)=>i(`debug`,e,t),info:(e,t)=>i(`info`,e,t),warn:(e,t)=>i(`warn`,e,t),error:(e,t)=>i(`error`,e,t),fatal:(e,t)=>i(`fatal`,e,t)}}async listTools(e){return(await this.loadTools(e)).map(({name:e,description:t,suggestionActions:n,inputSchema:r,capabilities:i})=>({name:e,description:t,suggestionActions:n,inputSchema:r,capabilities:i}))}async executeTool(e,t,n){let r=typeof n[I]==`string`?n[I]:void 0;return this.telemetry.runInSpan(`browse_tool.custom_tool.execute`,{attributes:{"browse_tool.tool.name":t,"browse_tool.custom_tools.directory":u.default.resolve(e),"browse_tool.page.id":r}},async r=>{let i=(await this.loadTools(e)).find(e=>e.name===t);if(!i)throw r?.setStatus({code:d.SpanStatusCode.ERROR,message:`Custom tool "${t}" not found`}),Error(`Custom tool "${t}" not found`);let a=n[I];if(typeof a!=`string`||a.length===0)throw r?.setStatus({code:d.SpanStatusCode.ERROR,message:`Custom tool "${t}" requires a string pageId`}),Error(`Custom tool "${t}" requires a string pageId`);let o=this.pageRegistry.get(a);if(!o)throw r?.setStatus({code:d.SpanStatusCode.ERROR,message:`Page "${a}" not found`}),Error(`Page "${a}" not found`);let s=this.resolveToolPage(t,a,o),c=this.createToolLogger(t,a,o);r?.setAttributes({"browse_tool.browser.id":o.browserId,"browse_tool.execution.mode":o.mode}),L(`Executing custom tool`,{toolName:t,directory:u.default.resolve(e),pageId:a,browserId:o.browserId,mode:o.mode,input:Me(n),scriptPath:i.scriptPath});let l;try{l=await i.execute?.({page:s,input:n,logger:c})}catch(e){let n=e instanceof Error?e.message:String(e);throw L(`Custom tool execution failed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,error:n,stack:e instanceof Error?e.stack:void 0}),Error(`Custom tool "${t}" failed on page "${a}": ${n}`,{cause:e})}L(`Custom tool execution completed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,result:Me(l)});let f=We(t,l,i.suggestionActions),p=f.isError?f.content[0]?.text:void 0;return p&&r?.setStatus({code:d.SpanStatusCode.ERROR,message:p}),f})}async loadTools(e){let t=u.default.resolve(e),n=u.default.join(t,`tools.yaml`),r=Re(await(0,h.readFile)(n,`utf8`).catch(e=>{throw Error(`Failed to read custom tool manifest at "${n}": ${e instanceof Error?e.message:String(e)}`)})),i=Ae.parse(r);return Promise.all(i.tools.map(async e=>{ze(e);let n=u.default.resolve(t,e.script);await(0,h.stat)(n).catch(t=>{throw Error(`Custom tool "${e.name}" script not found at "${n}": ${t instanceof Error?t.message:String(t)}`)});let r=Ve(n,await Be(n));return{name:e.name,description:e.description,suggestionActions:e.suggestionActions,inputSchema:e.inputSchema,capabilities:e.capabilities,scriptPath:n,...r}}))}};function Ke(t){let n=new o.Hono;return n.get(`/browsers`,async n=>{try{let r=t.get(e.g.BrowserService),i=t.get(e.g.PageRegistry),a=r.listBrowsers(),o=i.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString(),pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt.toISOString()}))}}),c={totalBrowsers:a.length,totalPages:o.length};return n.json({browsers:s,stats:c})}catch(e){return console.error(`Failed to list browsers:`,e),n.json({error:`Failed to list browsers`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/browsers`,async n=>{try{return await t.get(e.g.BrowserService).closeAll(),n.json({success:!0,message:`All browsers closed`})}catch(e){return console.error(`Failed to close all browsers:`,e),n.json({error:`Failed to close all browsers`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/browsers/:id`,async n=>{try{let r=n.req.param(`id`),i=t.get(e.g.BrowserService);return i.getBrowser(r)?(await i.closeBrowser(r),n.json({success:!0,message:`Browser "${r}" closed`})):n.json({error:`Browser "${r}" not found`},404)}catch(e){return console.error(`Failed to close browser:`,e),n.json({error:`Failed to close browser`,message:e instanceof Error?e.message:String(e)},500)}}),n.delete(`/pages/:id`,async n=>{try{let r=n.req.param(`id`),i=t.get(e.g.PageRegistry).get(r);return i?i.page?(await i.page.close(),n.json({success:!0,message:`Page "${r}" closed`})):n.json({error:`Page "${r}" is in extension mode and cannot be closed via API`},400):n.json({error:`Page "${r}" not found`},404)}catch(e){return console.error(`Failed to close page:`,e),n.json({error:`Failed to close page`,message:e instanceof Error?e.message:String(e)},500)}}),n}function qe(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}function Je(e){let t=Math.floor(e/1e3),n=Math.floor(t/60),r=Math.floor(n/60);return r>0?`${r}h ${n%60}m`:n>0?`${n}m ${t%60}s`:`${t}s`}function Ye(e){return Je(new Date().getTime()-e.getTime())}const Xe=`dashboard-container`,Ze=`dashboard-header`,Qe=`btn`,$e=`btn-danger`,H=`table-container`,et=`browser-table`,tt=`stats-container`,U=`stat-card`,nt=`browser-row`,rt=`page-row`,W=`status-active`,it=`url-cell`,G=`timestamp-cell`,K=`actions-cell`,q=`kill-btn`,at=`refresh-btn`,J=`empty-state`,ot=`
5
6
  * {
6
7
  margin: 0;
7
8
  padding: 0;
@@ -242,7 +243,7 @@ const e=require(`./stdio-ynNFGBY4.cjs`);require(`./playwright-test-CnsuVfC9.cjs`
242
243
  margin-bottom: 0.5rem;
243
244
  color: #999;
244
245
  }
245
- `;function Ae({browsers:e}){return e.length===0?(0,g.jsx)(`div`,{class:I,children:(0,g.jsxs)(`div`,{class:G,children:[(0,g.jsx)(`h3`,{children:`No Active Browsers`}),(0,g.jsx)(`p`,{children:`Launch a browser using MCP tools to see it here.`})]})}):(0,g.jsx)(`div`,{class:I,children:(0,g.jsxs)(`table`,{class:`browser-table`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsxs)(`tr`,{children:[(0,g.jsx)(`th`,{children:`ID`}),(0,g.jsx)(`th`,{children:`Status`}),(0,g.jsx)(`th`,{children:`URL / Title`}),(0,g.jsx)(`th`,{children:`Created`}),(0,g.jsx)(`th`,{children:`Age`}),(0,g.jsx)(`th`,{children:`Actions`})]})}),(0,g.jsx)(`tbody`,{id:`browser-table-body`,children:e.map(e=>(0,g.jsxs)(g.Fragment,{children:[(0,g.jsxs)(`tr`,{class:R,children:[(0,g.jsx)(`td`,{children:e.id}),(0,g.jsx)(`td`,{children:(0,g.jsxs)(`span`,{class:B,children:[e.pages.length,` page`,e.pages.length===1?``:`s`]})}),(0,g.jsx)(`td`,{children:e.profileName||`Default Profile`}),(0,g.jsx)(`td`,{class:H,children:P(e.createdAt)}),(0,g.jsx)(`td`,{class:H,children:F(e.createdAt)}),(0,g.jsx)(`td`,{class:U,children:(0,g.jsx)(`button`,{class:W,onclick:`dashboard.killBrowser('${e.id}')`,children:`Kill`})})]}),e.pages.map(t=>(0,g.jsxs)(`tr`,{class:z,children:[(0,g.jsxs)(`td`,{children:[t.id,e.currentPageId===t.id&&` (active)`]}),(0,g.jsx)(`td`,{children:(0,g.jsx)(`span`,{class:B,children:`Open`})}),(0,g.jsx)(`td`,{class:V,title:t.url,children:t.title||t.url||`about:blank`}),(0,g.jsx)(`td`,{class:H,children:P(t.createdAt)}),(0,g.jsx)(`td`,{class:H,children:F(t.createdAt)}),(0,g.jsx)(`td`,{class:U,children:(0,g.jsx)(`button`,{class:W,onclick:`dashboard.killPage('${t.id}')`,children:`Close`})})]}))]}))})]})})}function je({stats:e}){return(0,g.jsxs)(`div`,{class:`stats-container`,children:[(0,g.jsxs)(`div`,{class:L,children:[(0,g.jsx)(`h3`,{children:`Active Browsers`}),(0,g.jsx)(`div`,{class:`value`,children:e.totalBrowsers})]}),(0,g.jsxs)(`div`,{class:L,children:[(0,g.jsx)(`h3`,{children:`Active Pages`}),(0,g.jsx)(`div`,{class:`value`,children:e.totalPages})]})]})}function Me({browsers:e,stats:t}){return(0,g.jsxs)(`div`,{class:`dashboard-container`,children:[(0,g.jsxs)(`div`,{class:`dashboard-header`,children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`h1`,{children:`Playwright MCP Dashboard`}),(0,g.jsx)(`p`,{children:`Browser automation management (auto-refresh every 3 seconds)`})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`button`,{id:`refresh-btn`,class:`btn refresh-btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`}),(0,g.jsx)(`button`,{id:`kill-all-btn`,class:`btn btn-danger`,onclick:`dashboard.killAllBrowsers()`,children:`Kill All Browsers`})]})]}),(0,g.jsx)(je,{stats:t}),(0,g.jsx)(Ae,{browsers:e}),(0,g.jsx)(`script`,{children:(0,h.raw)(`
246
+ `;function st({browsers:e}){return e.length===0?(0,y.jsx)(`div`,{class:H,children:(0,y.jsxs)(`div`,{class:J,children:[(0,y.jsx)(`h3`,{children:`No Active Browsers`}),(0,y.jsx)(`p`,{children:`Launch a browser using MCP tools to see it here.`})]})}):(0,y.jsx)(`div`,{class:H,children:(0,y.jsxs)(`table`,{class:`browser-table`,children:[(0,y.jsx)(`thead`,{children:(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`th`,{children:`ID`}),(0,y.jsx)(`th`,{children:`Status`}),(0,y.jsx)(`th`,{children:`URL / Title`}),(0,y.jsx)(`th`,{children:`Created`}),(0,y.jsx)(`th`,{children:`Age`}),(0,y.jsx)(`th`,{children:`Actions`})]})}),(0,y.jsx)(`tbody`,{id:`browser-table-body`,children:e.map(e=>(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`tr`,{class:nt,children:[(0,y.jsx)(`td`,{children:e.id}),(0,y.jsx)(`td`,{children:(0,y.jsxs)(`span`,{class:W,children:[e.pages.length,` page`,e.pages.length===1?``:`s`]})}),(0,y.jsx)(`td`,{children:e.profileName||`Default Profile`}),(0,y.jsx)(`td`,{class:G,children:qe(e.createdAt)}),(0,y.jsx)(`td`,{class:G,children:Ye(e.createdAt)}),(0,y.jsx)(`td`,{class:K,children:(0,y.jsx)(`button`,{class:q,onclick:`dashboard.killBrowser('${e.id}')`,children:`Kill`})})]}),e.pages.map(t=>(0,y.jsxs)(`tr`,{class:rt,children:[(0,y.jsxs)(`td`,{children:[t.id,e.currentPageId===t.id&&` (active)`]}),(0,y.jsx)(`td`,{children:(0,y.jsx)(`span`,{class:W,children:`Open`})}),(0,y.jsx)(`td`,{class:it,title:t.url,children:t.title||t.url||`about:blank`}),(0,y.jsx)(`td`,{class:G,children:qe(t.createdAt)}),(0,y.jsx)(`td`,{class:G,children:Ye(t.createdAt)}),(0,y.jsx)(`td`,{class:K,children:(0,y.jsx)(`button`,{class:q,onclick:`dashboard.killPage('${t.id}')`,children:`Close`})})]}))]}))})]})})}function ct({stats:e}){return(0,y.jsxs)(`div`,{class:`stats-container`,children:[(0,y.jsxs)(`div`,{class:U,children:[(0,y.jsx)(`h3`,{children:`Active Browsers`}),(0,y.jsx)(`div`,{class:`value`,children:e.totalBrowsers})]}),(0,y.jsxs)(`div`,{class:U,children:[(0,y.jsx)(`h3`,{children:`Active Pages`}),(0,y.jsx)(`div`,{class:`value`,children:e.totalPages})]})]})}function lt({browsers:e,stats:t}){return(0,y.jsxs)(`div`,{class:`dashboard-container`,children:[(0,y.jsxs)(`div`,{class:`dashboard-header`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`h1`,{children:`Playwright MCP Dashboard`}),(0,y.jsx)(`p`,{children:`Browser automation management (auto-refresh every 3 seconds)`})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`button`,{id:`refresh-btn`,class:`btn refresh-btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`}),(0,y.jsx)(`button`,{id:`kill-all-btn`,class:`btn btn-danger`,onclick:`dashboard.killAllBrowsers()`,children:`Kill All Browsers`})]})]}),(0,y.jsx)(ct,{stats:t}),(0,y.jsx)(st,{browsers:e}),(0,y.jsx)(`script`,{children:(0,v.raw)(`
246
247
  class DashboardManager {
247
248
  constructor() {
248
249
  this.autoRefreshInterval = null;
@@ -280,7 +281,7 @@ class DashboardManager {
280
281
  }
281
282
 
282
283
  updateStats(stats) {
283
- const cards = document.querySelectorAll('.${L} .value');
284
+ const cards = document.querySelectorAll('.${U} .value');
284
285
  if (cards.length >= 2) {
285
286
  cards[0].textContent = stats.totalBrowsers;
286
287
  cards[1].textContent = stats.totalPages;
@@ -292,10 +293,10 @@ class DashboardManager {
292
293
  if (!tbody) return;
293
294
 
294
295
  if (browsers.length === 0) {
295
- const container = tbody.closest('.${I}');
296
+ const container = tbody.closest('.${H}');
296
297
  if (container) {
297
298
  container.innerHTML = \`
298
- <div class="${G}">
299
+ <div class="${J}">
299
300
  <h3>No Active Browsers</h3>
300
301
  <p>Launch a browser using MCP tools to see it here.</p>
301
302
  </div>
@@ -309,29 +310,29 @@ class DashboardManager {
309
310
  browsers.forEach(browser => {
310
311
  // Browser row
311
312
  const browserRow = document.createElement('tr');
312
- browserRow.className = '${R}';
313
+ browserRow.className = '${nt}';
313
314
  browserRow.innerHTML = \`
314
315
  <td>\${browser.id}</td>
315
- <td><span class="${B}">\${browser.pages.length} page\${browser.pages.length !== 1 ? 's' : ''}</span></td>
316
+ <td><span class="${W}">\${browser.pages.length} page\${browser.pages.length !== 1 ? 's' : ''}</span></td>
316
317
  <td>\${browser.profileName || 'Default Profile'}</td>
317
- <td class="${H}">\${this.formatTimestamp(browser.createdAt)}</td>
318
- <td class="${H}">\${this.formatAge(browser.createdAt)}</td>
319
- <td class="${U}"><button class="${W}" onclick="dashboard.killBrowser('\${browser.id}')">Kill</button></td>
318
+ <td class="${G}">\${this.formatTimestamp(browser.createdAt)}</td>
319
+ <td class="${G}">\${this.formatAge(browser.createdAt)}</td>
320
+ <td class="${K}"><button class="${q}" onclick="dashboard.killBrowser('\${browser.id}')">Kill</button></td>
320
321
  \`;
321
322
  tbody.appendChild(browserRow);
322
323
 
323
324
  // Page rows
324
325
  browser.pages.forEach(page => {
325
326
  const pageRow = document.createElement('tr');
326
- pageRow.className = '${z}';
327
+ pageRow.className = '${rt}';
327
328
  const isActive = browser.currentPageId === page.id;
328
329
  pageRow.innerHTML = \`
329
330
  <td>\${page.id}\${isActive ? ' (active)' : ''}</td>
330
- <td><span class="${B}">Open</span></td>
331
- <td class="${V}" title="\${this.escapeHtml(page.url)}">\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>
332
- <td class="${H}">\${this.formatTimestamp(page.createdAt)}</td>
333
- <td class="${H}">\${this.formatAge(page.createdAt)}</td>
334
- <td class="${U}"><button class="${W}" onclick="dashboard.killPage('\${page.id}')">Close</button></td>
331
+ <td><span class="${W}">Open</span></td>
332
+ <td class="${it}" title="\${this.escapeHtml(page.url)}">\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>
333
+ <td class="${G}">\${this.formatTimestamp(page.createdAt)}</td>
334
+ <td class="${G}">\${this.formatAge(page.createdAt)}</td>
335
+ <td class="${K}"><button class="${q}" onclick="dashboard.killPage('\${page.id}')">Close</button></td>
335
336
  \`;
336
337
  tbody.appendChild(pageRow);
337
338
  });
@@ -427,7 +428,7 @@ const dashboard = new DashboardManager();
427
428
  window.addEventListener('beforeunload', () => {
428
429
  dashboard.stopAutoRefresh();
429
430
  });
430
- `)})]})}function K({title:e,children:t}){return(0,g.jsxs)(`html`,{lang:`en`,children:[(0,g.jsxs)(`head`,{children:[(0,g.jsx)(`meta`,{charset:`UTF-8`}),(0,g.jsx)(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),(0,g.jsx)(`title`,{children:e}),(0,g.jsx)(`style`,{children:(0,h.raw)(`
431
+ `)})]})}function ut({title:e,children:t}){return(0,y.jsxs)(`html`,{lang:`en`,children:[(0,y.jsxs)(`head`,{children:[(0,y.jsx)(`meta`,{charset:`UTF-8`}),(0,y.jsx)(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),(0,y.jsx)(`title`,{children:e}),(0,y.jsx)(`style`,{children:(0,v.raw)(`
431
432
  * {
432
433
  margin: 0;
433
434
  padding: 0;
@@ -668,7 +669,7 @@ window.addEventListener('beforeunload', () => {
668
669
  margin-bottom: 0.5rem;
669
670
  color: #999;
670
671
  }
671
- `)})]}),(0,g.jsx)(`body`,{children:t})]})}function Ne(t){let n=new o.Hono;return n.get(`/`,async n=>{try{let r=t.get(e.p.BrowserService),i=t.get(e.p.PageRegistry),a=r.listBrowsers(),o=i.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt,pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt}))}}),c={totalBrowsers:a.length,totalPages:o.length};return n.html((0,g.jsx)(K,{title:`Playwright MCP Dashboard`,children:(0,g.jsx)(Me,{browsers:s,stats:c})}))}catch(e){return console.error(`Failed to render dashboard:`,e),n.html((0,g.jsx)(K,{title:`Playwright MCP Dashboard - Error`,children:(0,g.jsxs)(`div`,{style:`padding: 2rem; text-align: center;`,children:[(0,g.jsx)(`h1`,{children:`Failed to load dashboard`}),(0,g.jsx)(`p`,{children:e instanceof Error?e.message:String(e)}),(0,g.jsx)(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),n}function Pe(t){let n=new o.Hono;n.use(`*`,(0,s.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`DELETE`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),n.get(`/health`,n=>{try{let r=t.get(e.p.BrowserService).listBrowsers(),i={status:`healthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,timestamp:new Date().toISOString(),browsers:{count:r.length,instances:r.map(e=>({id:e.id,pageCount:e.pageIds.size,createdAt:e.createdAt.toISOString()}))}};return n.json(i)}catch(e){return n.json({status:`unhealthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,error:e instanceof Error?e.message:String(e)},503)}}),n.post(`/execute`,async n=>{try{let r=await n.req.json();if(!r.tool)return n.json({success:!1,error:`Missing "tool" field in request body`},400);let i=t.getAll(e.p.Tool).find(e=>e.getDefinition().name===r.tool);if(!i)return n.json({success:!1,error:`Tool "${r.tool}" not found`},404);let a=await i.execute(r.arguments||{}),o=(r.arguments||{}).pageId;if(o){let n=t.get(e.p.PageRegistry),r=t.get(e.p.BrowserService);n.touchPage(o);let i=n.get(o);i&&r.touchBrowser(i.browserId)}let s=a.isError?a.content[0]?.text:void 0;return n.json({success:!a.isError,result:a,error:s})}catch(e){return n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/browsers`,n=>{try{let r=t.get(e.p.BrowserService).listBrowsers();return n.json({browsers:r.map(e=>({id:e.id,profileName:e.profileName,pageIds:Array.from(e.pageIds),currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString()}))})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/tools`,n=>{try{let r=t.getAll(e.p.Tool);return n.json({tools:r.map(e=>e.getDefinition())})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}});let r=v(t);n.route(`/extension`,r);let i=be(t);n.route(`/api`,i);let a=Ne(t);return n.route(`/`,a),n}function Fe(t,n,r){t.get(`/ws/extension/:browserId`,r(t=>{let r=t.req.param(`browserId`),i=null;return{onOpen(t,a){try{i=n.get(e.p.WebSocketHub).addConnection(a,r)}catch{a.close(1011,`Internal server error`)}},onMessage(t){if(i)try{let r=n.get(e.p.WebSocketHub),a=typeof t.data==`string`?t.data:t.data.toString();r.handleMessage(i,a)}catch{}},onClose(){if(i)try{n.get(e.p.WebSocketHub).removeConnection(i)}catch{}},onError(){if(i)try{n.get(e.p.WebSocketHub).removeConnection(i)}catch{}}}})),t.get(`/ws/stats`,t=>{try{let r=n.get(e.p.WebSocketHub).getStats();return t.json({totalConnections:r.totalConnections,browserCount:r.browserCount,connections:r.connections.map(e=>({id:e.id,browserId:e.browserId,connectedAt:e.connectedAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}})}const Ie=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function Le(e=process.cwd()){let t=d.default.resolve(e);for(;;){for(let e of Ie)if((0,l.existsSync)(d.default.join(t,e)))return t;let e=d.default.dirname(t);if(e===t)return process.cwd();t=e}}const Re=new t.Command(`http-serve`).description(`Start HTTP server for browser automation`).option(`-p, --port <port>`,`Port to listen on`,String(e.s)).option(`--host <host>`,`Host to bind`,e.l()).option(`--headless`,`Run browsers in headless mode by default`,!0).option(`--idle-timeout <minutes>`,`Idle timeout in minutes before closing browsers`,`30`).option(`--registry-dir <path>`,`Custom registry path or directory for service discovery`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking (deprecated)`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(t){let r=O(`httpServe`),i={port:k(this,`port`,t.port,r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),headless:k(this,`headless`,t.headless,r.headless),idleTimeout:k(this,`idleTimeout`,t.idleTimeout,r.idleTimeout===void 0?void 0:String(r.idleTimeout)),host:k(this,`host`,t.host,r.host,process.env.PLAYWRIGHT_HOST),registryDir:k(this,`registryDir`,t.registryDir,r.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),registryPath:k(this,`registryPath`,t.registryPath,r.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),pidsDir:k(this,`pidsDir`,t.pidsDir,r.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:k(this,`profilesDir`,t.profilesDir,r.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)};try{let t=Number.parseInt(i.port,10),r=`browse-tool-http`,a=process.env.NODE_ENV||`development`,o=Le(process.cwd()),s=i.registryPath||i.registryDir||process.env.PLAYWRIGHT_REGISTRY_PATH;s&&(process.env.PLAYWRIGHT_REGISTRY_DIR=s,process.env.PORT_REGISTRY_PATH=s),process.env.PLAYWRIGHT_HOST=i.host,process.env.PLAYWRIGHT_PORT=i.port,i.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=i.pidsDir),i.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=i.profilesDir),console.log(`Starting HTTP server for browser automation...`),console.log(` Port: ${t}`),console.log(` Host: ${i.host}`),console.log(` Headless: ${i.headless}`),console.log(` Idle Timeout: ${i.idleTimeout} minutes`);let c=e.i(),l=Pe(c),{injectWebSocket:u,upgradeWebSocket:d}=(0,m.createNodeWebSocket)({app:l});Fe(l,c,d);let f=c.get(e.p.WebSocketHub),h=c.get(e.p.ExtensionTaskQueue),g=c.get(e.p.BrowserService),_=c.get(e.p.PageRegistry),v=c.get(e.p.IdleCleanupService);v.start(),f.setEventHandlers({onSessionRegister:(e,t)=>{if(t.type===`session:register`){let n=g.listBrowsers().find(e=>(e.mode===`extension`||e.mode===`vm`)&&!f.hasConnection(e.id)),r,i;if(n)r=n.id,i=n.currentPageId||Array.from(n.pageIds)[0]||``,f.reassignConnection(e.id,r),console.log(`[WebSocket] Extension connected to existing browser: ${r}, pageId: ${i}`);else{r=e.browserId,g.registerExtensionBrowserWithId(r),i=_.registerExtensionPage(r);let t=g.getBrowser(r);t&&(t.pageIds.add(i),t.currentPageId=i),console.log(`[WebSocket] Extension connected with new browser: ${r}, pageId: ${i}`)}let a=`session-${Date.now()}`;f.sendSessionAck(e,t.id??``,a,`extension`),f.broadcastPageCreated(r,i)}},onTaskResult:(e,t)=>{t.type===`task:result`&&h.submitResult({taskId:t.payload.taskId,success:t.payload.success,result:t.payload.result,error:t.payload.error})},onDisconnect:e=>{console.log(`[WebSocket] Extension disconnected: ${e.browserId}`)}});let y=new p.PortRegistryService(process.env.PORT_REGISTRY_PATH);s?console.log(` Registry path: ${s}`):console.log(` Registry path: default (~/.port-registry/ports.json)`);let b=await y.reservePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,preferredPort:t,pid:process.pid,host:i.host,force:!0,portRange:{min:t,max:t},metadata:{healthCheckUrl:`${e.c(i.host,t)}/health`,headless:i.headless,idleTimeout:i.idleTimeout}});if(!b.success||!b.record)throw Error(b.error||`Failed to reserve port ${t} in global registry`);let x=t,S;try{S=(0,n.serve)({fetch:l.fetch,port:x,hostname:i.host})}catch(e){throw await y.releasePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,pid:process.pid}),e}u(S);let C=e.c(i.host,x);console.log(`HTTP server listening on ${C}`),console.log(` Health check: ${C}/health`),console.log(` Execute tool: POST ${C}/execute`),console.log(`
672
- Press Ctrl+C to stop the server`);let w=async t=>{console.log(`\n\n${t} received. Shutting down gracefully...`);try{v.stop(),S.close(),console.log(`Server closed`);try{await c.get(e.p.BrowserService).closeAll(),console.log(`All browsers closed`)}catch{}try{await y.releasePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,pid:process.pid}),console.log(`Service deregistered`)}catch{}console.log(`Goodbye!`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>w(`SIGINT`)),process.on(`SIGTERM`,()=>w(`SIGTERM`))}catch(e){console.error(`Error starting HTTP server:`,e),process.exit(1)}}),ze={browser_click:[`input`],browser_fill:[`input`],browser_type:[`input`],browser_select:[`input`],browser_hover:[`input`],browser_drag:[`input`],browser_press_key:[`input`],browser_upload_file:[`input`],browser_navigate:[`navigation`],browser_go_back:[`navigation`],browser_go_forward:[`navigation`],browser_reload:[`navigation`],browser_wait_for:[`navigation`],browser_snapshot:[`snapshot`],browser_screenshot:[`snapshot`],browser_pdf:[`snapshot`],browser_list_pages:[`page`],browser_new_page:[`page`],browser_select_page:[`page`],browser_close_page:[`page`],browser_resize_page:[`page`,`emulation`],browser_handle_dialog:[`dialog`],browser_list_network_requests:[`network`],browser_get_network_request:[`network`],browser_list_console_messages:[`console`],browser_evaluate_script:[`script`],browser_emulate:[`emulation`],browser_expect:[`testing`],browser_start_trace:[`tracing`],browser_stop_trace:[`tracing`],browser_list_profiles:[`profile`],browser_delete_profile:[`profile`],run_spec:[`spec`,`testing`],discover_specs:[`spec`,`testing`],browser_launch:[`browser`],browser_close:[`browser`],browser_run_code:[`code`,`script`]};function Be(e){let t=new Set;for(let[n,r]of Object.entries(ze))r.some(t=>e.includes(t))&&t.add(n);return t}const Ve=3e3,He=4,Ue=150,We=[`browser_launch`],Ge=[`browser_new_page`];function q(e){if(!e?.content?.[0]?.text)return null;try{let t=JSON.parse(e.content[0].text);return{browserId:t.browserId,pageId:t.pageId,mode:t.mode,url:t.url}}catch{return null}}async function Ke(e){await new Promise(t=>setTimeout(t,e))}async function qe(e){let t=new AbortController,n=setTimeout(()=>t.abort(),3e3);try{let n=await fetch(`${e}/tools`,{signal:t.signal});if(!n.ok)throw Error(`HTTP error ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);if(!Array.isArray(r.tools))throw Error(`Invalid /tools response: missing tools array`);if(r.tools.length===0)throw Error(`HTTP server returned an empty tool list`);return r.tools}finally{clearTimeout(n)}}async function Je(e){let t;for(let n=1;n<=4;n+=1)try{return await qe(e)}catch(e){t=e instanceof Error?e:Error(String(e)),n<4&&await Ke(150*n)}throw Error(`Failed to fetch tools after 4 attempts: ${t?.message??`Unknown error`}`,{cause:t})}function Ye(e){if(!e?.tags?.length&&!e?.exclude?.length)return null;let t=e.tags?.length?Be(e.tags):null,n=new Set(e.exclude??[]);if(!t)return n.size>0?n:null;for(let e of n)t.delete(e);return t}function Xe(e){let{httpBaseUrl:t,sessionTracker:n,defaultMode:i,toolFilter:o}=e,s=o?.tags?.length?Ye(o):null,c=new Set(o?.exclude??[]),l=s!==null||c.size>0,u=new r.Server({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),d={name:`browser_list_session`,description:`List all browsers and pages created in this MCP session. Use this to see what resources you have available.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}},f=[];function p(e){return l?e.filter(e=>c.has(e.name)?!1:s===null?!0:s.has(e.name)):e}function m(e){return c.has(e)?!1:s===null?!0:s.has(e)}return u.setRequestHandler(a.ListToolsRequestSchema,async()=>{try{let e=await Je(t);f=e;let r=p(e);return n&&r.push(d),{tools:r}}catch(e){if(f.length>0){console.error(`Failed to refresh tools from HTTP server. Returning cached tools:`,e instanceof Error?e.message:String(e));let t=p(f);return n&&t.push(d),{tools:t}}let r=e instanceof Error?e.message:String(e);throw Error(`Failed to list tools from HTTP server at ${t}: ${r}`,{cause:e instanceof Error?e:void 0})}}),u.setRequestHandler(a.CallToolRequestSchema,async e=>{let{name:r,arguments:a}=e.params;if(r!==`browser_list_session`&&!m(r))return{content:[{type:`text`,text:`Tool "${r}" is not available (filtered by --tags or --exclude)`}],isError:!0};if(r===`browser_list_session`&&n){let e=n.getSessionState();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}let o=a||{};r===`browser_launch`&&i&&(o={...o,mode:i});try{let e=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:r,arguments:o})});if(!e.ok){let t=await e.text();return{content:[{type:`text`,text:`HTTP error ${e.status}: ${t}`}],isError:!0}}let i=await e.json();if(!i.success)return{content:[{type:`text`,text:i.error||i.result?.content?.[0]?.text||`Unknown error from HTTP server`}],isError:!0};if(n&&i.result){if(We.includes(r)){let e=q(i.result);e?.browserId&&(n.trackBrowser(e.browserId,e.mode||`playwright`),e.pageId&&n.trackPage(e.pageId,e.browserId,e.url))}else if(Ge.includes(r)){let e=q(i.result);e?.pageId&&e?.browserId&&n.trackPage(e.pageId,e.browserId,e.url)}}return i.result||{content:[{type:`text`,text:`No result returned`}]}}catch(e){return{content:[{type:`text`,text:`Failed to execute tool via HTTP server: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),u}const J=`stdio`,Ze=new Set([J]),Y=`chromium`,X=new Set([Y,`firefox`,`webkit`]),Z=new Set([`playwright`,`extension`,`vm`,`stealth`]),Qe=`browser_close`,$e=`,`,et=`spawned`,tt=`reused`,Q=`SIGINT`,nt=`SIGTERM`,rt=`PLAYWRIGHT_REGISTRY_DIR`,it=`PORT_REGISTRY_PATH`,at=`PLAYWRIGHT_PIDS_DIR`,ot=`PLAYWRIGHT_PROFILES_DIR`,st=`PLAYWRIGHT_HOST`,ct=`PLAYWRIGHT_PORT`,lt=0,ut=1;var dt=class extends Error{code=`INVALID_TRANSPORT`;recovery=`Use --type ${J} (currently the only supported transport)`;transportType;constructor(e,t){super(`Unknown transport type: ${e}`,t),this.name=`InvalidTransportError`,this.transportType=e}},ft=class extends Error{code=`INVALID_BROWSER_TYPE`;recovery=`Use --browser ${[...X].join(`, `)}`;browserType;constructor(e,t){super(`Unknown browser type: ${e}`,t),this.name=`InvalidBrowserTypeError`,this.browserType=e}},pt=class extends Error{code=`INVALID_LAUNCH_MODE`;recovery=`Use --mode ${[...Z].join(`, `)}`;launchMode;constructor(e,t){super(`Unknown launch mode: ${e}`,t),this.name=`InvalidLaunchModeError`,this.launchMode=e}};function mt(e){let t=e.toLowerCase();if(!X.has(t))throw new ft(e);return t}function ht(e){let t=e.toLowerCase();if(!Z.has(t))throw new pt(e);return t}function gt(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function _t(e){let t=e.tags?gt(e.tags):void 0,n=e.exclude?gt(e.exclude):void 0;if(!(!t?.length&&!n?.length))return{tags:t,exclude:n}}var vt=class extends Error{code=`BROWSER_CLEANUP_FAILED`;recovery=`Browsers may still be running; check the HTTP server or kill processes manually`;failedBrowserIds;httpBaseUrl;constructor(e,t,n){super(`Failed to close ${e.length} browser(s): ${e.join(`, `)}`,n),this.name=`BrowserCleanupError`,this.failedBrowserIds=e,this.httpBaseUrl=t}},yt=class extends Error{code=`SESSION_SHUTDOWN_FAILED`;recovery=`Check logs for cleanup and transport errors; processes may need manual cleanup`;signal;constructor(e,t){super(`Shutdown failed for signal ${e}`,t),this.name=`SessionShutdownError`,this.signal=e}},bt=class extends Error{code=`HTTP_SERVER_START_FAILED`;recovery=`Check if the HTTP server binary exists and the port is available`;constructor(e){super(`Failed to start HTTP server`,e),this.name=`HttpServerStartError`}},xt=class extends Error{code=`MCP_SERVER_BOOTSTRAP_FAILED`;recovery=`Check if all dependencies are installed and try again`;constructor(e){super(`Failed to start MCP server`,e),this.name=`McpServerBootstrapError`}};async function St(e,t){let n=e.getBrowserIds(),r=[],i;for(let e of n)try{let n=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:`browser_close`,arguments:{browserId:e}})});if(!n.ok){let e=await n.text().catch(()=>``);throw Error(`HTTP ${n.status} from ${t}/execute: ${e}`)}console.error(` Closed browser: ${e}`)}catch(t){let n=t instanceof Error?t:Error(String(t));console.error(` Failed to close browser: ${e}`,n),r.push(e),i??=n}if(r.length>0)throw new vt(r,t,{cause:i});e.clear()}async function Ct(e,t,n){await e.start();let r=!1,i=async i=>{if(r)return;r=!0,console.error(`\nReceived ${i}, shutting down gracefully...`);let a;try{let e=t.getSessionState();e.totalBrowsers>0&&(console.error(` Cleaning up ${e.totalBrowsers} browser(s) from this session...`),await St(t,n))}catch(e){a=e instanceof Error?e:Error(String(e)),console.error(`Browser cleanup error:`,a)}finally{try{await e.stop()}catch(e){let t=e instanceof Error?e:Error(String(e));console.error(`Transport stop error:`,t),a??=t}if(a){let e=new yt(i,{cause:a});console.error(`Error during shutdown:`,e),process.exit(1)}process.exit(0)}};process.once(Q,()=>i(Q)),process.once(nt,()=>i(nt))}const wt=new t.Command(`mcp-serve`).description(`Start Playwright MCP server for browser automation`).option(`-t, --type <type>`,`Transport type: ${J}`,J).option(`-b, --browser <browser>`,`Default browser type: ${[...X].join(`, `)}`,Y).option(`--headless`,`Run browsers in headless mode by default`,!1).option(`--no-headless`,`Run browsers in headed mode (visible window)`).option(`--host <host>`,`Default host for HTTP services`,e.l()).option(`-p, --profile <name>`,`Default profile name for browser sessions`).option(`-m, --mode <mode>`,`Default launch mode: ${[...Z].join(`, `)}`).option(`--tags <tags>`,`Comma-separated tags to filter tools (e.g. input,navigation,snapshot)`).option(`--exclude <tools>`,`Comma-separated tool names to exclude (applied after --tags)`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(t){let n=O(`mcpServe`),r={type:k(this,`type`,t.type,n.type),browser:k(this,`browser`,t.browser,n.browser),headless:k(this,`headless`,t.headless,n.headless),profile:k(this,`profile`,t.profile,n.profile),mode:k(this,`mode`,t.mode,n.mode),host:k(this,`host`,t.host,n.host,process.env.PLAYWRIGHT_HOST),tags:k(this,`tags`,t.tags,n.tags),exclude:k(this,`exclude`,t.exclude,n.exclude),registryPath:k(this,`registryPath`,t.registryPath,n.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),registryDir:k(this,`registryDir`,t.registryDir,n.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),pidsDir:k(this,`pidsDir`,t.pidsDir,n.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:k(this,`profilesDir`,t.profilesDir,n.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)},i=r.type.toLowerCase();try{if(!Ze.has(i))throw new dt(i);let t=mt(r.browser),n=r.mode?ht(r.mode):void 0,a=_t(r);console.error(`Playwright MCP Server starting...`),console.error(` Transport: ${i}`),console.error(` Default browser: ${t}`),console.error(` Headless: ${r.headless}`),n&&console.error(` Default mode: ${n}`),r.profile&&console.error(` Profile: ${r.profile}`),a?.tags?.length&&console.error(` Tags: ${a.tags.join(`, `)}`),a?.exclude?.length&&console.error(` Exclude: ${a.exclude.join(`, `)}`);let o=r.registryPath||r.registryDir;o&&(process.env.PLAYWRIGHT_REGISTRY_DIR=o,process.env.PORT_REGISTRY_PATH=o,console.error(` Registry path: ${o}`)),r.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=r.pidsDir,console.error(` PIDs dir: ${r.pidsDir}`)),r.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=r.profilesDir,console.error(` Profiles dir: ${r.profilesDir}`)),r.host&&(process.env.PLAYWRIGHT_HOST=r.host),process.env.PLAYWRIGHT_PORT=e.u().toString();let s=await e.a().get(e.p.HttpServerManager).ensureRunning();if(!s.running)throw new bt;let c=e.c(e.l(),s.port),l=s.spawned?`spawned`:`reused`;console.error(` HTTP server: ${l} on port ${s.port}`);let u=new e.o;await Ct(new e.t(Xe({httpBaseUrl:c,sessionTracker:u,defaultMode:n,toolFilter:a})),u,c)}catch(e){(e instanceof dt||e instanceof ft||e instanceof pt||e instanceof bt)&&(console.error(`Error [${e.code}]: ${e.message}`),console.error(`Recovery: ${e.recovery}`),process.exit(1));let t=new xt({cause:e instanceof Error?e:Error(String(e))});console.error(`Error [${t.code}]: ${t.message}`,t.cause),console.error(`Recovery: ${t.recovery}`),process.exit(1)}}),Tt=new t.Command(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{let t=O(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??t.registryPath??t.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&t.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=t.pidsDir),console.log(`browse-tool Status
673
- `),console.log(`-`.repeat(50));let r=await e.r().get(e.p.HttpServerManager).getStatus();if(r.running){let n=process.env.PLAYWRIGHT_HOST??t.host??e.l();console.log(`HTTP Server: Running`),console.log(` PID: ${r.pid}`),console.log(` Port: ${r.port}`),console.log(` Health: ${e.c(n,r.port)}/health`),r.browserCount!==void 0&&console.log(` Browsers: ${r.browserCount}`)}else console.log(`HTTP Server: Not Running`),r.error&&console.log(` Error: ${r.error}`);console.log(``),console.log(`-`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),Et=new t.Command(`stop`).description(`Stop HTTP server and clean up registry/PID files`).action(async()=>{try{let t=O(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??t.registryPath??t.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&t.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=t.pidsDir),console.log(`Stopping browse-tool services...`),await e.r().get(e.p.HttpServerManager).stop()?(console.log(`HTTP server stopped`),console.log(`Registry and PID files cleaned up`)):console.log(`No HTTP server was running`),console.log(`Done!`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),Dt=new Set([`--config`]);function Ot(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(Dt.has(n)){t+=1;continue}if(![...Dt].some(e=>n.startsWith(`${e}=`))&&!n.startsWith(`-`))return n}}function kt(e,t=!1){if(t)return!1;let n=Ot(e);return n?n===`tools`?!0:n===`help`&&e.includes(`tools`):!1}function At(e){return e.replace(/_/g,`-`)}function $(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function jt(e){let t=new Set;e.type&&t.add(e.type);for(let n of e.oneOf??[])n.type&&t.add(n.type);return t}function Mt(e,t){let n=jt(t);if(t.type===`number`||t.type===`integer`){let t=Number(e);if(Number.isNaN(t))throw Error(`Invalid number: ${e}`);return t}if(t.type===`boolean`){if(e===`true`||e===`1`)return!0;if(e===`false`||e===`0`)return!1;throw Error(`Invalid boolean: ${e}`)}if(n.has(`array`)||n.has(`object`))try{return JSON.parse(e)}catch{if(t.type===`array`||t.type===`object`)throw Error(`Invalid JSON: ${e}`)}if(n.has(`number`)||n.has(`integer`)){let t=Number(e);if(!Number.isNaN(t)&&e.trim()!==``)return t}return e}function Nt(e,t){let n=$(e);return t.type===`boolean`?`--${n}`:`--${n} <value>`}function Pt(n){let{definition:r,execute:i}=n,a=new t.Command(At(r.name));a.description(r.description);let o=r.inputSchema,s=o.properties||{},c=new Set(o.required||[]);for(let[e,n]of Object.entries(s)){let r=n,i=Nt(e,r),o=r.description||``;r.enum&&r.enum.length>0&&(o+=` (choices: ${r.enum.join(`, `)})`),r.default!==void 0&&(o+=` (default: ${JSON.stringify(r.default)})`),c.has(e)&&(o+=` [required]`),r.type===`boolean`?r.default===!0?a.option(`--no-${$(e)}`,`Disable ${o}`):a.option(i,o,r.default):r.enum&&r.enum.length>0?a.addOption(new t.Option(i,o).choices(r.enum)):a.option(i,o)}return a.action(async function(){let t=this.optsWithGlobals(),n=O(`tools`),a=k(this,`format`,t.format??`json`,n.format),o=k(this,`color`,t.color??!0,n.color),l=k(this,`port`,String(t.port??e.s),n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),u={format:a,color:o},d=le(r.name);try{let e={};for(let[n,r]of Object.entries(s)){let i=r,a=$(n).replace(/-([a-z])/g,(e,t)=>t.toUpperCase()),o=this.getOptionValueSourceWithGlobals(a),s=t[a];i.type===`boolean`&&i.default===!0&&(s=t[a]),(o===void 0||o===`default`||o===`implied`)&&d[n]!==void 0&&(s=d[n]),s!==void 0&&(typeof s==`string`&&i.type!==`string`?e[n]=Mt(s,i):e[n]=s)}for(let t of c)if(e[t]===void 0){let e=$(t);console.error(N(`Missing required option: --${e}`,u.color)),process.exit(1)}let n=Number.parseInt(l,10),a=A({port:n}),o=i?await i(e,{command:this,formatterOptions:u,port:n}):await a.execute(r.name,e),f=fe(o,u);f&&console.log(f),o.isError&&process.exit(1)}catch(e){console.error(N(e instanceof Error?e:String(e),u.color)),process.exit(1)}}),a}function Ft(e){return e.map(Pt)}const It=`browser_list_session`,Lt=`List browsers and pages currently tracked by the browse-tool HTTP server.`,Rt={name:`browser_list_session`,description:`List browsers and pages currently tracked by the browse-tool HTTP server.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}};function zt(){return new t.Command(`tools`).description(`Execute browser automation tools`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s))}function Bt(e){return{browserCount:e.length,browsers:e.map(e=>({browserId:e.id,profileName:e.profileName,currentPageId:e.currentPageId,pageIds:e.pageIds,createdAt:e.createdAt}))}}function Vt(){return{definition:Rt,execute:async(e,t)=>{let n=await A({port:t.port}).listBrowsers();return{content:[{type:`text`,text:JSON.stringify(Bt(n),null,2)}]}}}}function Ht(e){return[...e.map(e=>({definition:e})),Vt()]}async function Ut(t,n){let r=O(`tools`),i=Number(n?.port??process.env.PLAYWRIGHT_PORT??r.port??e.s);try{let e=Ft(Ht(await A({port:i}).listTools()));for(let n of e)t.addCommand(n)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`${N(`Failed to load tool commands: ${t}`,!0)}\n`),process.stderr.write(`Make sure the HTTP server is running: browse-tool http-serve
674
- `)}}const Wt=`PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS`;async function Gt(){let n=ce(process.argv.slice(2)),r=process.env.PLAYWRIGHT_PORT??n.config.commands.tools?.port??n.config.commands.exec?.port??e.s,i=new t.Command;i.name(`browse-tool`).description(`MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support`).option(`--config <path>`,`Path to browse-tool config file or config directory`).version(_),i.addCommand(wt),i.addCommand(Re),i.addCommand(x),i.addCommand(Et),i.addCommand(Tt),i.addCommand(ye);let a=zt();kt(process.argv.slice(2),process.env.PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS===`1`)&&await Ut(a,{port:Number(r)}),i.addCommand(a),await i.parseAsync(process.argv)}Gt().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
672
+ `)})]}),(0,y.jsx)(`body`,{children:t})]})}function dt(t){let n=new o.Hono;return n.get(`/`,async n=>{try{let r=t.get(e.g.BrowserService),i=t.get(e.g.PageRegistry),a=r.listBrowsers(),o=i.list(),s=a.map(e=>{let t=o.filter(t=>t.browserId===e.id);return{id:e.id,profileName:e.profileName,currentPageId:e.currentPageId,createdAt:e.createdAt,pages:t.map(e=>({id:e.id,url:e.url,title:e.title,createdAt:e.createdAt}))}}),c={totalBrowsers:a.length,totalPages:o.length};return n.html((0,y.jsx)(ut,{title:`Playwright MCP Dashboard`,children:(0,y.jsx)(lt,{browsers:s,stats:c})}))}catch(e){return console.error(`Failed to render dashboard:`,e),n.html((0,y.jsx)(ut,{title:`Playwright MCP Dashboard - Error`,children:(0,y.jsxs)(`div`,{style:`padding: 2rem; text-align: center;`,children:[(0,y.jsx)(`h1`,{children:`Failed to load dashboard`}),(0,y.jsx)(`p`,{children:e instanceof Error?e.message:String(e)}),(0,y.jsx)(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),n}function ft(e){if(!e.isError)return;let t=e.content[0];return t?.type===`text`&&typeof t.text==`string`?t.text:`Unknown tool execution error`}function pt(t){try{return t.get(e.g.TelemetryService)}catch{return new e.m}}function mt(t){let n=new o.Hono,r=t.get(e.g.PageRegistry),i=t.get(e.g.ExtensionTaskQueue),a=pt(t),c=new Ge(r,i,a);n.use(`*`,(0,s.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`DELETE`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),n.get(`/health`,n=>{try{let r=t.get(e.g.BrowserService).listBrowsers(),i={status:`healthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,timestamp:new Date().toISOString(),browsers:{count:r.length,instances:r.map(e=>({id:e.id,pageCount:e.pageIds.size,createdAt:e.createdAt.toISOString()}))}};return n.json(i)}catch(e){return n.json({status:`unhealthy`,service:`browse-tool-http`,serviceName:`browse-tool-http`,error:e instanceof Error?e.message:String(e)},503)}}),n.post(`/execute`,async n=>{try{let i=await n.req.json();if(!i.tool)return n.json({success:!1,error:`Missing "tool" field in request body`},400);let o=typeof i.arguments?.pageId==`string`?i.arguments.pageId:void 0;return await a.runInSpan(`browse_tool.http.execute`,{attributes:{"http.method":`POST`,"http.route":`/execute`,"browse_tool.tool.name":i.tool,"browse_tool.page.id":o}},async o=>{let s=t.getAll(e.g.Tool).find(e=>e.getDefinition().name===i.tool);if(!s)return o?.setStatus({code:d.SpanStatusCode.ERROR,message:`Tool "${i.tool}" not found`}),a.log(`warn`,`browse-tool HTTP execute rejected unknown tool`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":i.tool}}),n.json({success:!1,error:`Tool "${i.tool}" not found`},404);let c=await s.execute(i.arguments||{}),l=i.arguments||{},u=typeof l.pageId==`string`?l.pageId:void 0;if(u){let n=t.get(e.g.BrowserService);r.touchPage(u);let i=r.get(u);i&&n.touchBrowser(i.browserId)}let f=ft(c);return f?(o?.setStatus({code:d.SpanStatusCode.ERROR,message:f}),a.log(`error`,`browse-tool HTTP execute failed`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":i.tool,"browse_tool.page.id":u}})):a.log(`info`,`browse-tool HTTP execute succeeded`,{attributes:{"http.route":`/execute`,"browse_tool.tool.name":i.tool,"browse_tool.page.id":u}}),n.json({success:!c.isError,result:c,error:f})})}catch(e){return a.log(`error`,`browse-tool HTTP execute request crashed`,{attributes:{"http.route":`/execute`},exception:e}),n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/browsers`,n=>{try{let r=t.get(e.g.BrowserService).listBrowsers();return n.json({browsers:r.map(e=>({id:e.id,profileName:e.profileName,pageIds:Array.from(e.pageIds),currentPageId:e.currentPageId,createdAt:e.createdAt.toISOString()}))})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/tools`,n=>{try{let r=t.getAll(e.g.Tool);return n.json({tools:r.map(e=>e.getDefinition())})}catch(e){return n.json({error:e instanceof Error?e.message:String(e)},500)}}),n.get(`/custom-tools`,async e=>{let t=e.req.query(`dir`);if(!t)return e.json({tools:[],error:`Missing "dir" query parameter`},400);try{let n=await c.listTools(t);return e.json({tools:n})}catch(t){return e.json({tools:[],error:t instanceof Error?t.message:String(t)},400)}}),n.post(`/custom-tools`,async n=>{let i=n.req.query(`dir`);if(!i)return n.json({success:!1,error:`Missing "dir" query parameter`},400);try{let o=await n.req.json();if(!o.tool)return n.json({success:!1,error:`Missing "tool" field in request body`},400);let s=typeof o.arguments?.pageId==`string`?o.arguments.pageId:void 0;return await a.runInSpan(`browse_tool.http.custom_tool.execute`,{attributes:{"http.method":`POST`,"http.route":`/custom-tools`,"browse_tool.tool.name":o.tool,"browse_tool.page.id":s,"browse_tool.custom_tools.directory":i}},async l=>{let u=await c.executeTool(i,o.tool,o.arguments||{});if(s){let n=t.get(e.g.BrowserService);r.touchPage(s);let i=r.get(s);i&&n.touchBrowser(i.browserId)}let f=ft(u);return f?(l?.setStatus({code:d.SpanStatusCode.ERROR,message:f}),a.log(`error`,`browse-tool custom tool execution failed`,{attributes:{"http.route":`/custom-tools`,"browse_tool.tool.name":o.tool,"browse_tool.page.id":s}})):a.log(`info`,`browse-tool custom tool execution succeeded`,{attributes:{"http.route":`/custom-tools`,"browse_tool.tool.name":o.tool,"browse_tool.page.id":s}}),n.json({success:!u.isError,result:u,error:f})})}catch(e){return a.log(`error`,`browse-tool custom tool request crashed`,{attributes:{"http.route":`/custom-tools`,"browse_tool.custom_tools.directory":i},exception:e}),n.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}});let l=x(t);n.route(`/extension`,l);let u=Ke(t);n.route(`/api`,u);let f=dt(t);return n.route(`/`,f),n}function ht(t,n,r){t.get(`/ws/extension/:browserId`,r(t=>{let r=t.req.param(`browserId`),i=null;return{onOpen(t,a){try{i=n.get(e.g.WebSocketHub).addConnection(a,r)}catch{a.close(1011,`Internal server error`)}},onMessage(t){if(i)try{let r=n.get(e.g.WebSocketHub),a=typeof t.data==`string`?t.data:t.data.toString();r.handleMessage(i,a)}catch{}},onClose(){if(i)try{n.get(e.g.WebSocketHub).removeConnection(i)}catch{}},onError(){if(i)try{n.get(e.g.WebSocketHub).removeConnection(i)}catch{}}}})),t.get(`/ws/stats`,t=>{try{let r=n.get(e.g.WebSocketHub).getStats();return t.json({totalConnections:r.totalConnections,browserCount:r.browserCount,connections:r.connections.map(e=>({id:e.id,browserId:e.browserId,connectedAt:e.connectedAt.toISOString()}))})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}})}const gt=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function _t(e=process.cwd()){let t=u.default.resolve(e);for(;;){for(let e of gt)if((0,l.existsSync)(u.default.join(t,e)))return t;let e=u.default.dirname(t);if(e===t)return process.cwd();t=e}}const vt=new t.Command(`http-serve`).description(`Start HTTP server for browser automation`).option(`-p, --port <port>`,`Port to listen on`,String(e.s)).option(`--host <host>`,`Host to bind`,e.l()).option(`--headless`,`Run browsers in headless mode by default`,!0).option(`--idle-timeout <minutes>`,`Idle timeout in minutes before closing browsers`,`30`).option(`--registry-dir <path>`,`Custom registry path or directory for service discovery`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking (deprecated)`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(t){let r=D(`httpServe`),i={port:O(this,`port`,t.port,r.port===void 0?void 0:String(r.port),process.env.PLAYWRIGHT_PORT),headless:O(this,`headless`,t.headless,r.headless),idleTimeout:O(this,`idleTimeout`,t.idleTimeout,r.idleTimeout===void 0?void 0:String(r.idleTimeout)),host:O(this,`host`,t.host,r.host,process.env.PLAYWRIGHT_HOST),registryDir:O(this,`registryDir`,t.registryDir,r.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),registryPath:O(this,`registryPath`,t.registryPath,r.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),pidsDir:O(this,`pidsDir`,t.pidsDir,r.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:O(this,`profilesDir`,t.profilesDir,r.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)};try{let t=Number.parseInt(i.port,10),r=`browse-tool-http`,a=process.env.NODE_ENV||`development`,o=_t(process.cwd()),s=i.registryPath||i.registryDir||process.env.PLAYWRIGHT_REGISTRY_PATH;s&&(process.env.PLAYWRIGHT_REGISTRY_DIR=s,process.env.PORT_REGISTRY_PATH=s),process.env.PLAYWRIGHT_HOST=i.host,process.env.PLAYWRIGHT_PORT=i.port,i.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=i.pidsDir),i.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=i.profilesDir),console.log(`Starting HTTP server for browser automation...`),console.log(` Port: ${t}`),console.log(` Host: ${i.host}`),console.log(` Headless: ${i.headless}`),console.log(` Idle Timeout: ${i.idleTimeout} minutes`);let c=e.i(),l=mt(c),{injectWebSocket:u,upgradeWebSocket:d}=(0,_.createNodeWebSocket)({app:l});ht(l,c,d);let p=c.get(e.g.WebSocketHub),m=c.get(e.g.ExtensionTaskQueue),h=c.get(e.g.BrowserService),g=c.get(e.g.PageRegistry),v=c.get(e.g.IdleCleanupService);v.start(),p.setEventHandlers({onSessionRegister:(e,t)=>{if(t.type===`session:register`){let n=h.listBrowsers().find(e=>(e.mode===`extension`||e.mode===`vm`)&&!p.hasConnection(e.id)),r,i;if(n)r=n.id,i=n.currentPageId||Array.from(n.pageIds)[0]||``,p.reassignConnection(e.id,r),console.log(`[WebSocket] Extension connected to existing browser: ${r}, pageId: ${i}`);else{r=e.browserId,h.registerExtensionBrowserWithId(r),i=g.registerExtensionPage(r);let t=h.getBrowser(r);t&&(t.pageIds.add(i),t.currentPageId=i),console.log(`[WebSocket] Extension connected with new browser: ${r}, pageId: ${i}`)}let a=`session-${Date.now()}`;p.sendSessionAck(e,t.id??``,a,`extension`),p.broadcastPageCreated(r,i)}},onTaskResult:(e,t)=>{t.type===`task:result`&&m.submitResult({taskId:t.payload.taskId,success:t.payload.success,result:t.payload.result,error:t.payload.error})},onDisconnect:e=>{console.log(`[WebSocket] Extension disconnected: ${e.browserId}`)}});let y=new f.PortRegistryService(process.env.PORT_REGISTRY_PATH);s?console.log(` Registry path: ${s}`):console.log(` Registry path: default (~/.port-registry/ports.json)`);let b=await y.reservePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,preferredPort:t,pid:process.pid,host:i.host,force:!0,portRange:{min:t,max:t},metadata:{healthCheckUrl:`${e.c(i.host,t)}/health`,headless:i.headless,idleTimeout:i.idleTimeout}});if(!b.success||!b.record)throw Error(b.error||`Failed to reserve port ${t} in global registry`);let x=t,S;try{S=(0,n.serve)({fetch:l.fetch,port:x,hostname:i.host})}catch(e){throw await y.releasePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,pid:process.pid}),e}u(S);let C=e.c(i.host,x);console.log(`HTTP server listening on ${C}`),console.log(` Health check: ${C}/health`),console.log(` Execute tool: POST ${C}/execute`),console.log(`
673
+ Press Ctrl+C to stop the server`);let w=async t=>{console.log(`\n\n${t} received. Shutting down gracefully...`);try{v.stop(),S.close(),console.log(`Server closed`);try{await c.get(e.g.BrowserService).closeAll(),console.log(`All browsers closed`)}catch{}try{await y.releasePort({repositoryPath:o,serviceName:r,serviceType:`tool`,environment:a,pid:process.pid}),console.log(`Service deregistered`)}catch{}console.log(`Goodbye!`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>w(`SIGINT`)),process.on(`SIGTERM`,()=>w(`SIGTERM`))}catch(e){console.error(`Error starting HTTP server:`,e),process.exit(1)}}),yt={browser_click:[`input`],browser_fill:[`input`],browser_type:[`input`],browser_select:[`input`],browser_hover:[`input`],browser_drag:[`input`],browser_press_key:[`input`],browser_upload_file:[`input`],browser_navigate:[`navigation`],browser_go_back:[`navigation`],browser_go_forward:[`navigation`],browser_reload:[`navigation`],browser_wait_for:[`navigation`],browser_snapshot:[`snapshot`],browser_screenshot:[`snapshot`],browser_pdf:[`snapshot`],browser_list_pages:[`page`],browser_new_page:[`page`],browser_select_page:[`page`],browser_close_page:[`page`],browser_resize_page:[`page`,`emulation`],browser_handle_dialog:[`dialog`],browser_list_network_requests:[`network`],browser_get_network_request:[`network`],browser_list_console_messages:[`console`],browser_evaluate_script:[`script`],browser_emulate:[`emulation`],browser_expect:[`testing`],browser_start_trace:[`tracing`],browser_stop_trace:[`tracing`],browser_list_profiles:[`profile`],browser_delete_profile:[`profile`],run_spec:[`spec`,`testing`],discover_specs:[`spec`,`testing`],browser_launch:[`browser`],browser_close:[`browser`],browser_run_code:[`code`,`script`]};function bt(e){let t=new Set;for(let[n,r]of Object.entries(yt))r.some(t=>e.includes(t))&&t.add(n);return t}const xt=3e3,St=4,Ct=150,wt=[`browser_launch`],Tt=[`browser_new_page`];function Et(e){if(!e?.content?.[0]?.text)return null;try{let t=JSON.parse(e.content[0].text);return{browserId:t.browserId,pageId:t.pageId,mode:t.mode,url:t.url}}catch{return null}}async function Dt(e){await new Promise(t=>setTimeout(t,e))}async function Ot(e){let t=new AbortController,n=setTimeout(()=>t.abort(),xt);try{let n=await fetch(`${e}/tools`,{signal:t.signal});if(!n.ok)throw Error(`HTTP error ${n.status}: ${n.statusText}`);let r=await n.json();if(r.error)throw Error(r.error);if(!Array.isArray(r.tools))throw Error(`Invalid /tools response: missing tools array`);if(r.tools.length===0)throw Error(`HTTP server returned an empty tool list`);return r.tools}finally{clearTimeout(n)}}function kt(e,t){let n=new URL(`/custom-tools`,e);return n.searchParams.set(`dir`,t),n.toString()}async function At(e,t){let n=new AbortController,r=setTimeout(()=>n.abort(),xt);try{let r=await fetch(kt(e,t),{signal:n.signal});if(!r.ok)throw Error(`HTTP error ${r.status}: ${r.statusText}`);let i=await r.json();if(i.error)throw Error(i.error);if(!Array.isArray(i.tools))throw Error(`Invalid /custom-tools response: missing tools array`);return i.tools}finally{clearTimeout(r)}}async function jt(e){let t;for(let n=1;n<=4;n+=1)try{return await Ot(e)}catch(e){t=e instanceof Error?e:Error(String(e)),n<4&&await Dt(150*n)}throw Error(`Failed to fetch tools after 4 attempts: ${t?.message??`Unknown error`}`,{cause:t})}async function Mt(e,t){let n;for(let r=1;r<=4;r+=1)try{return await At(e,t)}catch(e){n=e instanceof Error?e:Error(String(e)),r<4&&await Dt(150*r)}throw Error(`Failed to fetch custom tools after 4 attempts: ${n?.message??`Unknown error`}`,{cause:n})}function Nt(e){if(!e?.tags?.length&&!e?.exclude?.length)return null;let t=e.tags?.length?bt(e.tags):null,n=new Set(e.exclude??[]);if(!t)return n.size>0?n:null;for(let e of n)t.delete(e);return t}function Pt(e){let{httpBaseUrl:t,sessionTracker:n,defaultMode:i,toolFilter:o,customToolsDir:s}=e,c=o?.tags?.length?Nt(o):null,l=new Set(o?.exclude??[]),u=c!==null||l.size>0,d=new r.Server({name:`browse-tool`,version:`0.1.0`},{capabilities:{tools:{}}}),f={name:`browser_list_session`,description:`List all browsers and pages created in this MCP session. Use this to see what resources you have available.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}},p=[],m=[];function h(e){return u?e.filter(e=>l.has(e.name)?!1:c===null?!0:c.has(e.name)):e}function g(e){return e.filter(e=>!l.has(e.name))}function _(e){return{name:e.name,description:e.description,inputSchema:e.inputSchema,annotations:e.capabilities}}function v(e){return l.has(e)?!1:c===null?!0:c.has(e)}return d.setRequestHandler(a.ListToolsRequestSchema,async()=>{try{let e=await jt(t);p=e;let r=s?await Mt(t,s):[];m=r;let i=[...h(e),...g(r).map(e=>_(e))];return n&&i.push(f),{tools:i}}catch(e){if(p.length>0||m.length>0){console.error(`Failed to refresh tools from HTTP server. Returning cached tools:`,e instanceof Error?e.message:String(e));let t=[...h(p),...g(m).map(e=>_(e))];return n&&t.push(f),{tools:t}}let r=e instanceof Error?e.message:String(e);throw Error(`Failed to list tools from HTTP server at ${t}: ${r}`,{cause:e})}}),d.setRequestHandler(a.CallToolRequestSchema,async e=>{let{name:r,arguments:a}=e.params,o=new Set(m.map(e=>e.name));if(r!==`browser_list_session`&&!v(r))return{content:[{type:`text`,text:`Tool "${r}" is not available (filtered by --tags or --exclude)`}],isError:!0};if(r===`browser_list_session`&&n){let e=n.getSessionState();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}let c=a||{};r===`browser_launch`&&i&&(c={...c,mode:i});try{s&&!o.has(r)&&(m=await Mt(t,s),o=new Set(m.map(e=>e.name)));let e=o.has(r),i=await fetch(e&&s?kt(t,s):`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:r,arguments:c})});if(!i.ok){let e=await i.text();return{content:[{type:`text`,text:`HTTP error ${i.status}: ${e}`}],isError:!0}}let a=await i.json();if(!a.success)return{content:[{type:`text`,text:a.error||a.result?.content?.[0]?.text||`Unknown error from HTTP server`}],isError:!0};if(n&&a.result){if(wt.includes(r)){let e=Et(a.result);e?.browserId&&(n.trackBrowser(e.browserId,e.mode||`playwright`),e.pageId&&n.trackPage(e.pageId,e.browserId,e.url))}else if(Tt.includes(r)){let e=Et(a.result);e?.pageId&&e?.browserId&&n.trackPage(e.pageId,e.browserId,e.url)}}return a.result||{content:[{type:`text`,text:`No result returned`}]}}catch(e){return{content:[{type:`text`,text:`Failed to execute tool via HTTP server: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}),d}const Y=`stdio`,Ft=new Set([Y]),It=`chromium`,X=new Set([It,`firefox`,`webkit`]),Z=new Set([`playwright`,`extension`,`vm`,`stealth`]),Lt=`browser_close`,Rt=`,`,zt=`spawned`,Bt=`reused`,Vt=`SIGINT`,Ht=`SIGTERM`,Ut=`PLAYWRIGHT_REGISTRY_DIR`,Wt=`PORT_REGISTRY_PATH`,Gt=`PLAYWRIGHT_PIDS_DIR`,Kt=`PLAYWRIGHT_PROFILES_DIR`,qt=`PLAYWRIGHT_HOST`,Jt=`PLAYWRIGHT_PORT`,Yt=0,Xt=1;var Zt=class extends Error{code=`INVALID_TRANSPORT`;recovery=`Use --type ${Y} (currently the only supported transport)`;transportType;constructor(e,t){super(`Unknown transport type: ${e}`,t),this.name=`InvalidTransportError`,this.transportType=e}},Qt=class extends Error{code=`INVALID_BROWSER_TYPE`;recovery=`Use --browser ${[...X].join(`, `)}`;browserType;constructor(e,t){super(`Unknown browser type: ${e}`,t),this.name=`InvalidBrowserTypeError`,this.browserType=e}},$t=class extends Error{code=`INVALID_LAUNCH_MODE`;recovery=`Use --mode ${[...Z].join(`, `)}`;launchMode;constructor(e,t){super(`Unknown launch mode: ${e}`,t),this.name=`InvalidLaunchModeError`,this.launchMode=e}};function en(e){let t=e.toLowerCase();if(!X.has(t))throw new Qt(e);return t}function tn(e){let t=e.toLowerCase();if(!Z.has(t))throw new $t(e);return t}function nn(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function rn(e){let t=e.tags?nn(e.tags):void 0,n=e.exclude?nn(e.exclude):void 0;if(!(!t?.length&&!n?.length))return{tags:t,exclude:n}}var an=class extends Error{code=`BROWSER_CLEANUP_FAILED`;recovery=`Browsers may still be running; check the HTTP server or kill processes manually`;failedBrowserIds;httpBaseUrl;constructor(e,t,n){super(`Failed to close ${e.length} browser(s): ${e.join(`, `)}`,n),this.name=`BrowserCleanupError`,this.failedBrowserIds=e,this.httpBaseUrl=t}},on=class extends Error{code=`SESSION_SHUTDOWN_FAILED`;recovery=`Check logs for cleanup and transport errors; processes may need manual cleanup`;signal;constructor(e,t){super(`Shutdown failed for signal ${e}`,t),this.name=`SessionShutdownError`,this.signal=e}},sn=class extends Error{code=`HTTP_SERVER_START_FAILED`;recovery=`Check if the HTTP server binary exists and the port is available`;constructor(e){super(`Failed to start HTTP server`,e),this.name=`HttpServerStartError`}},cn=class extends Error{code=`MCP_SERVER_BOOTSTRAP_FAILED`;recovery=`Check if all dependencies are installed and try again`;constructor(e){super(`Failed to start MCP server`,e),this.name=`McpServerBootstrapError`}};async function ln(e,t){let n=e.getBrowserIds(),r=[],i;for(let e of n)try{let n=await fetch(`${t}/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tool:`browser_close`,arguments:{browserId:e}})});if(!n.ok){let e=await n.text().catch(()=>``);throw Error(`HTTP ${n.status} from ${t}/execute: ${e}`)}console.error(` Closed browser: ${e}`)}catch(t){let n=t instanceof Error?t:Error(String(t));console.error(` Failed to close browser: ${e}`,n),r.push(e),i??=n}if(r.length>0)throw new an(r,t,{cause:i});e.clear()}async function un(e,t,n){await e.start();let r=!1,i=async i=>{if(r)return;r=!0,console.error(`\nReceived ${i}, shutting down gracefully...`);let a;try{let e=t.getSessionState();e.totalBrowsers>0&&(console.error(` Cleaning up ${e.totalBrowsers} browser(s) from this session...`),await ln(t,n))}catch(e){a=e instanceof Error?e:Error(String(e)),console.error(`Browser cleanup error:`,a)}finally{try{await e.stop()}catch(e){let t=e instanceof Error?e:Error(String(e));console.error(`Transport stop error:`,t),a??=t}if(a){let e=new on(i,{cause:a});console.error(`Error during shutdown:`,e),process.exit(1)}process.exit(0)}};process.once(Vt,()=>i(Vt)),process.once(Ht,()=>i(Ht))}const dn=new t.Command(`mcp-serve`).description(`Start Playwright MCP server for browser automation`).option(`-t, --type <type>`,`Transport type: ${Y}`,Y).option(`-b, --browser <browser>`,`Default browser type: ${[...X].join(`, `)}`,It).option(`--headless`,`Run browsers in headless mode by default`,!1).option(`--no-headless`,`Run browsers in headed mode (visible window)`).option(`--host <host>`,`Default host for HTTP services`,e.l()).option(`-p, --profile <name>`,`Default profile name for browser sessions`).option(`-m, --mode <mode>`,`Default launch mode: ${[...Z].join(`, `)}`).option(`--tags <tags>`,`Comma-separated tags to filter tools (e.g. input,navigation,snapshot)`).option(`--exclude <tools>`,`Comma-separated tool names to exclude (applied after --tags)`).option(`--custom-tools <path>`,`Path to a folder containing tools.yaml and custom tool scripts`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).option(`--pids-dir <path>`,`Custom PIDs directory for process tracking`).option(`--profiles-dir <path>`,`Custom profiles directory for browser profiles`).action(async function(t){let n=D(`mcpServe`),r={type:O(this,`type`,t.type,n.type),browser:O(this,`browser`,t.browser,n.browser),headless:O(this,`headless`,t.headless,n.headless),profile:O(this,`profile`,t.profile,n.profile),mode:O(this,`mode`,t.mode,n.mode),host:O(this,`host`,t.host,n.host,process.env.PLAYWRIGHT_HOST),tags:O(this,`tags`,t.tags,n.tags),exclude:O(this,`exclude`,t.exclude,n.exclude),customTools:O(this,`customTools`,t.customTools,n.customTools),registryPath:O(this,`registryPath`,t.registryPath,n.registryPath,process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH),registryDir:O(this,`registryDir`,t.registryDir,n.registryDir,process.env.PLAYWRIGHT_REGISTRY_DIR),pidsDir:O(this,`pidsDir`,t.pidsDir,n.pidsDir,process.env.PLAYWRIGHT_PIDS_DIR),profilesDir:O(this,`profilesDir`,t.profilesDir,n.profilesDir,process.env.PLAYWRIGHT_PROFILES_DIR)},i=r.type.toLowerCase();try{if(!Ft.has(i))throw new Zt(i);let t=en(r.browser),n=r.mode?tn(r.mode):void 0,a=rn(r),o=r.customTools?u.default.resolve(r.customTools):void 0;console.error(`Playwright MCP Server starting...`),console.error(` Transport: ${i}`),console.error(` Default browser: ${t}`),console.error(` Headless: ${r.headless}`),n&&console.error(` Default mode: ${n}`),r.profile&&console.error(` Profile: ${r.profile}`),a?.tags?.length&&console.error(` Tags: ${a.tags.join(`, `)}`),a?.exclude?.length&&console.error(` Exclude: ${a.exclude.join(`, `)}`),o&&console.error(` Custom tools: ${o}`);let s=r.registryPath||r.registryDir;s&&(process.env.PLAYWRIGHT_REGISTRY_DIR=s,process.env.PORT_REGISTRY_PATH=s,console.error(` Registry path: ${s}`)),r.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=r.pidsDir,console.error(` PIDs dir: ${r.pidsDir}`)),r.profilesDir&&(process.env.PLAYWRIGHT_PROFILES_DIR=r.profilesDir,console.error(` Profiles dir: ${r.profilesDir}`)),r.host&&(process.env.PLAYWRIGHT_HOST=r.host),process.env.PLAYWRIGHT_PORT=e.u().toString();let c=await e.a().get(e.g.HttpServerManager).ensureRunning();if(!c.running)throw new sn;let l=e.c(e.l(),c.port),d=c.spawned?`spawned`:`reused`;console.error(` HTTP server: ${d} on port ${c.port}`);let f=new e.o;await un(new e.t(Pt({httpBaseUrl:l,sessionTracker:f,defaultMode:n,toolFilter:a,customToolsDir:o})),f,l)}catch(e){(e instanceof Zt||e instanceof Qt||e instanceof $t||e instanceof sn)&&(console.error(`Error [${e.code}]: ${e.message}`),console.error(`Recovery: ${e.recovery}`),process.exit(1));let t=new cn({cause:e instanceof Error?e:Error(String(e))});console.error(`Error [${t.code}]: ${t.message}`,t.cause),console.error(`Recovery: ${t.recovery}`),process.exit(1)}}),fn=new t.Command(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{let t=D(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??t.registryPath??t.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&t.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=t.pidsDir),console.log(`browse-tool Status
674
+ `),console.log(`-`.repeat(50));let r=await e.r().get(e.g.HttpServerManager).getStatus();if(r.running){let n=process.env.PLAYWRIGHT_HOST??t.host??e.l();console.log(`HTTP Server: Running`),console.log(` PID: ${r.pid}`),console.log(` Port: ${r.port}`),console.log(` Health: ${e.c(n,r.port)}/health`),r.browserCount!==void 0&&console.log(` Browsers: ${r.browserCount}`)}else console.log(`HTTP Server: Not Running`),r.error&&console.log(` Error: ${r.error}`);console.log(``),console.log(`-`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),pn=new t.Command(`stop`).description(`Stop HTTP server and clean up registry/PID files`).action(async()=>{try{let t=D(`httpServe`),n=process.env.PLAYWRIGHT_REGISTRY_PATH??process.env.PORT_REGISTRY_PATH??t.registryPath??t.registryDir;n&&(process.env.PLAYWRIGHT_REGISTRY_DIR=n,process.env.PLAYWRIGHT_REGISTRY_PATH=n,process.env.PORT_REGISTRY_PATH=n),!process.env.PLAYWRIGHT_PIDS_DIR&&t.pidsDir&&(process.env.PLAYWRIGHT_PIDS_DIR=t.pidsDir),console.log(`Stopping browse-tool services...`),await e.r().get(e.g.HttpServerManager).stop()?(console.log(`HTTP server stopped`),console.log(`Registry and PID files cleaned up`)):console.log(`No HTTP server was running`),console.log(`Done!`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),Q=new Set([`--config`]);function mn(e){for(let t=0;t<e.length;t+=1){let n=e[t];if(Q.has(n)){t+=1;continue}if(![...Q].some(e=>n.startsWith(`${e}=`))&&!n.startsWith(`-`))return n}}function hn(e,t=!1){if(t)return!1;let n=mn(e);return n?n===`tools`?!0:n===`help`&&e.includes(`tools`):!1}function gn(e){return e.replace(/_/g,`-`)}function $(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).toLowerCase()}function _n(e){let t=new Set;e.type&&t.add(e.type);for(let n of e.oneOf??[])n.type&&t.add(n.type);return t}function vn(e,t){let n=_n(t);if(t.type===`number`||t.type===`integer`){let t=Number(e);if(Number.isNaN(t))throw Error(`Invalid number: ${e}`);return t}if(t.type===`boolean`){if(e===`true`||e===`1`)return!0;if(e===`false`||e===`0`)return!1;throw Error(`Invalid boolean: ${e}`)}if(n.has(`array`)||n.has(`object`))try{return JSON.parse(e)}catch{if(t.type===`array`||t.type===`object`)throw Error(`Invalid JSON: ${e}`)}if(n.has(`number`)||n.has(`integer`)){let t=Number(e);if(!Number.isNaN(t)&&e.trim()!==``)return t}return e}function yn(e,t){let n=$(e);return t.type===`boolean`?`--${n}`:`--${n} <value>`}function bn(n){let{definition:r,execute:i}=n,a=new t.Command(gn(r.name));a.description(r.description);let o=r.inputSchema,s=o.properties||{},c=new Set(o.required||[]);for(let[e,n]of Object.entries(s)){let r=n,i=yn(e,r),o=r.description||``;r.enum&&r.enum.length>0&&(o+=` (choices: ${r.enum.join(`, `)})`),r.default!==void 0&&(o+=` (default: ${JSON.stringify(r.default)})`),c.has(e)&&(o+=` [required]`),r.type===`boolean`?r.default===!0?a.option(`--no-${$(e)}`,`Disable ${o}`):a.option(i,o,r.default):r.enum&&r.enum.length>0?a.addOption(new t.Option(i,o).choices(r.enum)):a.option(i,o)}return a.action(async function(){let t=this.optsWithGlobals(),n=D(`tools`),a=O(this,`format`,t.format??`json`,n.format),o=O(this,`color`,t.color??!0,n.color),l=O(this,`port`,String(t.port??e.s),n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),u={format:a,color:o},d=pe(r.name);try{let e={};for(let[n,r]of Object.entries(s)){let i=r,a=$(n).replace(/-([a-z])/g,(e,t)=>t.toUpperCase()),o=this.getOptionValueSourceWithGlobals(a),s=t[a];i.type===`boolean`&&i.default===!0&&(s=t[a]),(o===void 0||o===`default`||o===`implied`)&&d[n]!==void 0&&(s=d[n]),s!==void 0&&(typeof s==`string`&&i.type!==`string`?e[n]=vn(s,i):e[n]=s)}for(let t of c)if(e[t]===void 0){let e=$(t);console.error(M(`Missing required option: --${e}`,u.color)),process.exit(1)}let n=Number.parseInt(l,10),a=k({port:n}),o=i?await i(e,{command:this,formatterOptions:u,port:n}):await a.execute(r.name,e),f=j(o,u);f&&console.log(f),o.isError&&process.exit(1)}catch(e){console.error(M(e instanceof Error?e:String(e),u.color)),process.exit(1)}}),a}function xn(e){return e.map(bn)}const Sn=`browser_list_session`,Cn=`List browsers and pages currently tracked by the browse-tool HTTP server.`,wn={name:`browser_list_session`,description:`List browsers and pages currently tracked by the browse-tool HTTP server.`,inputSchema:{type:`object`,properties:{},required:[],additionalProperties:!1}};function Tn(){return new t.Command(`tools`).description(`Execute browser automation tools`).option(`-f, --format <format>`,`Output format: json, text, quiet`,`json`).option(`--no-color`,`Disable colored output`).option(`-p, --port <port>`,`HTTP server port`,String(e.s))}function En(e){return{browserCount:e.length,browsers:e.map(e=>({browserId:e.id,profileName:e.profileName,currentPageId:e.currentPageId,pageIds:e.pageIds,createdAt:e.createdAt}))}}function Dn(){return{definition:wn,execute:async(e,t)=>{let n=await k({port:t.port}).listBrowsers();return{content:[{type:`text`,text:JSON.stringify(En(n),null,2)}]}}}}function On(e){return[...e.map(e=>({definition:e})),Dn()]}async function kn(t,n){let r=D(`tools`),i=Number(n?.port??process.env.PLAYWRIGHT_PORT??r.port??e.s);try{let e=xn(On(await k({port:i}).listTools()));for(let n of e)t.addCommand(n)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`${M(`Failed to load tool commands: ${t}`,!0)}\n`),process.stderr.write(`Make sure the HTTP server is running: browse-tool http-serve
675
+ `)}}const An=`PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS`;async function jn(){let n=fe(process.argv.slice(2)),r=process.env.PLAYWRIGHT_PORT??n.config.commands.tools?.port??n.config.commands.exec?.port??e.s,i=new t.Command;i.name(`browse-tool`).description(`MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support`).option(`--config <path>`,`Path to browse-tool config file or config directory`).version(b),i.addCommand(dn),i.addCommand(vt),i.addCommand(w),i.addCommand(pn),i.addCommand(fn),i.addCommand(Te),i.addCommand(P),i.addCommand(F);let a=Tn();hn(process.argv.slice(2),process.env.PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS===`1`)&&await kn(a,{port:Number(r)}),i.addCommand(a),await i.parseAsync(process.argv)}jn().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});