@agimon-ai/browse-tool 0.2.1 → 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/dist/cli.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
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.1.0`;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(`
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
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
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`).alias(`list`).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`).alias(`exec`).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
+ `)}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=`
6
6
  * {
7
7
  margin: 0;
8
8
  padding: 0;
package/dist/cli.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import{a as e,c as t,d as n,f as r,g as i,h as a,i as o,l as s,m as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./stdio-ELROpCD_.mjs";import"./playwright-test-BwI7HgW7.mjs";import{stripTypeScriptTypes as h}from"node:module";import"reflect-metadata/lite";import{Command as g,Option as _}from"commander";import{serve as ee}from"@hono/node-server";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as b,ListToolsRequestSchema as x}from"@modelcontextprotocol/sdk/types.js";import{Hono as S}from"hono";import{cors as C}from"hono/cors";import{Container as te,ContainerModule as ne}from"inversify";import{existsSync as w,readFileSync as re,statSync as ie}from"node:fs";import T from"node:path";import{SpanStatusCode as E}from"@opentelemetry/api";import{PortRegistryService as ae}from"@agimon-ai/foundation-port-registry";import{homedir as oe}from"node:os";import{z as D}from"zod";import{readFile as se,stat as ce}from"node:fs/promises";import{createNodeWebSocket as le}from"@hono/node-ws";import{raw as ue}from"hono/html";import{Fragment as de,jsx as O,jsxs as k}from"hono/jsx/jsx-runtime";var fe=`0.1.0`;function pe(e){let t=new S;return t.get(`/telemetry-config`,e=>{try{let t=new URL(e.req.url).origin;return e.json(a(process.env,t))}catch(t){return e.json({enabled:!1,error:t instanceof Error?t.message:String(t)},500)}}),t.get(`/tasks`,t=>{try{let n=e.get(i.ExtensionTaskQueue).getNextTask();return n?t.json({task:{id:n.id,tool:n.tool,arguments:n.arguments,telemetry:n.telemetry}}):t.json({})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/result`,async t=>{try{let n=await t.req.json();if(!n.taskId)return t.json({success:!1,error:`Missing taskId in request body`},400);let r=e.get(i.ExtensionTaskQueue),a={taskId:n.taskId,success:n.success,result:n.result,error:n.error};return r.submitResult(a)?t.json({success:!0}):t.json({success:!1,error:`Task ${n.taskId} not found or already completed`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/status`,t=>{try{let n=e.get(i.ExtensionTaskQueue),r=n.getConnectionStatus(),a={connected:r.connected,lastPollAt:r.lastPollAt?.toISOString(),lastResultAt:r.lastResultAt?.toISOString(),pendingTasks:r.pendingTasks,queueSize:n.queueSize};return t.json(a)}catch(e){return t.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/register`,async t=>{try{let n=await t.req.json();if(!n.browserId)return t.json({success:!1,error:`Missing browserId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).register(n);return t.json({success:!0,session:{id:r.id,browserId:r.browserId,controlMode:r.controlMode,createdAt:r.createdAt.toISOString()}})}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/heartbeat`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).heartbeat(n);return r?t.json({success:!0,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested,lastHeartbeatAt:r.lastHeartbeatAt.toISOString()}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).requestHandoff(n);return r?t.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff/acknowledge`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).acknowledgeHandoff(n.sessionId);return r?t.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found or no handoff pending`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/sessions`,t=>{try{let n=e.get(i.ExtensionSessionRegistry).listSessions();return t.json({sessions:n.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 t.json({error:e instanceof Error?e.message:String(e)},500)}}),t}function me(){return new ne(e=>{e.bind(i.ExtensionTaskQueue).to(u).inSingletonScope(),e.bind(i.ExtensionToolDelegator).to(r).inSingletonScope()})}function he(e){let t=new v({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(x,async()=>({tools:n})),t.setRequestHandler(b,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const ge=new g(`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 e=>{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 t=typeof e.port==`string`?Number.parseInt(e.port,10):e.port;e.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${t}`),console.error(` Wait for extension: ${e.waitForExtension}`));let n=new te({defaultScope:`Singleton`});n.load(me());let r=n.get(i.ExtensionTaskQueue),a=n.get(i.ExtensionToolDelegator),o=new S;o.use(`*`,C({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let s=pe(n);o.route(`/extension`,s),o.get(`/health`,e=>{let t=r.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let c=ee({fetch:o.fetch,port:t});c.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${t} 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 ${t}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${t}/extension/tasks`),e.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{r.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let l=he(a),u=new y;await l.connect(u),console.error(`Chrome extension MCP server started on stdio`);let d=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{r.clearAllTasks(`Server shutting down`),await u.close(),c.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>d(`SIGINT`)),process.on(`SIGTERM`,()=>d(`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)}}),_e=`BROWSE_TOOL_CONFIG`,ve=`.browse-tool`,A=`config.json`,ye=[`json`,`text`,`quiet`],be={commands:{},tools:{}},xe=D.record(D.string(),D.unknown()),Se=D.object({mcpServe:D.object({type:D.string().optional(),browser:D.string().optional(),headless:D.boolean().optional(),profile:D.string().optional(),mode:D.string().optional(),host:D.string().optional(),tags:D.string().optional(),exclude:D.string().optional(),customTools:D.string().optional(),registryPath:D.string().optional(),registryDir:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),httpServe:D.object({port:D.coerce.number().int().positive().optional(),headless:D.boolean().optional(),idleTimeout:D.coerce.number().positive().optional(),host:D.string().optional(),registryDir:D.string().optional(),registryPath:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),exec:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),tools:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),status:D.record(D.string(),D.unknown()).optional(),stop:D.record(D.string(),D.unknown()).optional()}),Ce=D.object({commands:Se.default({}),tools:D.record(D.string(),xe).default({})});let j={config:be};function we(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 Te(e){let t=T.resolve(e);if(!w(t))throw Error(`Config path not found: ${t}`);return ie(t).isDirectory()?T.join(t,A):t}function Ee(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??oe(),a=we(e);if(a)return Te(a);if(n[_e])return Te(n[_e]);let o=T.join(r,ve,A);if(w(o))return o;let s=T.join(i,ve,A);if(w(s))return s}function De(e){let t=re(e,`utf8`),n=JSON.parse(t);return Ce.parse(n)}function Oe(e,t={}){let n=Ee(e,t);return n?{configPath:n,config:De(n)}:{config:be}}function ke(e,t={}){return j=Oe(e,t),j}function M(e){return(j.config.commands??{})[e]??{}}function Ae(e){return j.config.tools?.[e]??{}}function N(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}var je=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().get(i.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 P(e){return new je(e)}const Me={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 F(e,t,n){return n?`${Me[t]}${e}${Me.reset}`:e}function I(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?Ne(e):Pe(e,r)}function Ne(e){return JSON.stringify(e,null,2)}function Pe(e,t){let n=[];e.isError&&n.push(F(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(Fe(r,t)):r.type===`image`?n.push(Re(r,t)):n.push(F(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
2
+ import{a as e,c as t,d as n,f as r,g as i,h as a,i as o,l as s,m as c,o as l,p as u,r as d,s as f,t as p,u as m}from"./stdio-ELROpCD_.mjs";import"./playwright-test-BwI7HgW7.mjs";import{stripTypeScriptTypes as h}from"node:module";import"reflect-metadata/lite";import{Command as g,Option as _}from"commander";import{serve as ee}from"@hono/node-server";import{Server as v}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as b,ListToolsRequestSchema as x}from"@modelcontextprotocol/sdk/types.js";import{Hono as S}from"hono";import{cors as C}from"hono/cors";import{Container as te,ContainerModule as ne}from"inversify";import{existsSync as w,readFileSync as re,statSync as ie}from"node:fs";import T from"node:path";import{SpanStatusCode as E}from"@opentelemetry/api";import{PortRegistryService as ae}from"@agimon-ai/foundation-port-registry";import{homedir as oe}from"node:os";import{z as D}from"zod";import{readFile as se,stat as ce}from"node:fs/promises";import{createNodeWebSocket as le}from"@hono/node-ws";import{raw as ue}from"hono/html";import{Fragment as de,jsx as O,jsxs as k}from"hono/jsx/jsx-runtime";var fe=`0.2.1`;function pe(e){let t=new S;return t.get(`/telemetry-config`,e=>{try{let t=new URL(e.req.url).origin;return e.json(a(process.env,t))}catch(t){return e.json({enabled:!1,error:t instanceof Error?t.message:String(t)},500)}}),t.get(`/tasks`,t=>{try{let n=e.get(i.ExtensionTaskQueue).getNextTask();return n?t.json({task:{id:n.id,tool:n.tool,arguments:n.arguments,telemetry:n.telemetry}}):t.json({})}catch(e){return t.json({error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/result`,async t=>{try{let n=await t.req.json();if(!n.taskId)return t.json({success:!1,error:`Missing taskId in request body`},400);let r=e.get(i.ExtensionTaskQueue),a={taskId:n.taskId,success:n.success,result:n.result,error:n.error};return r.submitResult(a)?t.json({success:!0}):t.json({success:!1,error:`Task ${n.taskId} not found or already completed`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/status`,t=>{try{let n=e.get(i.ExtensionTaskQueue),r=n.getConnectionStatus(),a={connected:r.connected,lastPollAt:r.lastPollAt?.toISOString(),lastResultAt:r.lastResultAt?.toISOString(),pendingTasks:r.pendingTasks,queueSize:n.queueSize};return t.json(a)}catch(e){return t.json({connected:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/register`,async t=>{try{let n=await t.req.json();if(!n.browserId)return t.json({success:!1,error:`Missing browserId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).register(n);return t.json({success:!0,session:{id:r.id,browserId:r.browserId,controlMode:r.controlMode,createdAt:r.createdAt.toISOString()}})}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/heartbeat`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).heartbeat(n);return r?t.json({success:!0,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested,lastHeartbeatAt:r.lastHeartbeatAt.toISOString()}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).requestHandoff(n);return r?t.json({success:!0,message:`Handoff requested. AI will take control when ready.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.post(`/handoff/acknowledge`,async t=>{try{let n=await t.req.json();if(!n.sessionId)return t.json({success:!1,error:`Missing sessionId in request body`},400);let r=e.get(i.ExtensionSessionRegistry).acknowledgeHandoff(n.sessionId);return r?t.json({success:!0,message:`Handoff acknowledged. AI now has control.`,session:{id:r.id,controlMode:r.controlMode,handoffRequested:r.handoffRequested}}):t.json({success:!1,error:`Session ${n.sessionId} not found or no handoff pending`},404)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:String(e)},500)}}),t.get(`/sessions`,t=>{try{let n=e.get(i.ExtensionSessionRegistry).listSessions();return t.json({sessions:n.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 t.json({error:e instanceof Error?e.message:String(e)},500)}}),t}function me(){return new ne(e=>{e.bind(i.ExtensionTaskQueue).to(u).inSingletonScope(),e.bind(i.ExtensionToolDelegator).to(r).inSingletonScope()})}function he(e){let t=new v({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(x,async()=>({tools:n})),t.setRequestHandler(b,async t=>{let{name:n,arguments:r}=t.params;return await e.executeTool(n,r||{})}),t}const ge=new g(`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 e=>{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 t=typeof e.port==`string`?Number.parseInt(e.port,10):e.port;e.verbose&&(console.error(`Chrome Extension MCP Server starting...`),console.error(` HTTP Port: ${t}`),console.error(` Wait for extension: ${e.waitForExtension}`));let n=new te({defaultScope:`Singleton`});n.load(me());let r=n.get(i.ExtensionTaskQueue),a=n.get(i.ExtensionToolDelegator),o=new S;o.use(`*`,C({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`]}));let s=pe(n);o.route(`/extension`,s),o.get(`/health`,e=>{let t=r.getConnectionStatus();return e.json({status:`healthy`,service:`browse-tool-chrome`,extension:t})});let c=ee({fetch:o.fetch,port:t});c.on(`error`,e=>{e.code===`EADDRINUSE`?(console.error(`Error [PORT_IN_USE]: Port ${t} 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 ${t}`),console.error(`Waiting for Chrome extension to connect...`),console.error(`Extension should poll: http://localhost:${t}/extension/tasks`),e.waitForExtension&&(console.error(`Waiting for extension connection before starting MCP...`),await new Promise(e=>{let t=setInterval(()=>{r.getConnectionStatus().connected&&(clearInterval(t),console.error(`Extension connected!`),e())},500)}));let l=he(a),u=new y;await l.connect(u),console.error(`Chrome extension MCP server started on stdio`);let d=async e=>{console.error(`\nReceived ${e}, shutting down gracefully...`);try{r.clearAllTasks(`Server shutting down`),await u.close(),c.close(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>d(`SIGINT`)),process.on(`SIGTERM`,()=>d(`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)}}),_e=`BROWSE_TOOL_CONFIG`,ve=`.browse-tool`,A=`config.json`,ye=[`json`,`text`,`quiet`],be={commands:{},tools:{}},xe=D.record(D.string(),D.unknown()),Se=D.object({mcpServe:D.object({type:D.string().optional(),browser:D.string().optional(),headless:D.boolean().optional(),profile:D.string().optional(),mode:D.string().optional(),host:D.string().optional(),tags:D.string().optional(),exclude:D.string().optional(),customTools:D.string().optional(),registryPath:D.string().optional(),registryDir:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),httpServe:D.object({port:D.coerce.number().int().positive().optional(),headless:D.boolean().optional(),idleTimeout:D.coerce.number().positive().optional(),host:D.string().optional(),registryDir:D.string().optional(),registryPath:D.string().optional(),pidsDir:D.string().optional(),profilesDir:D.string().optional()}).partial().optional(),exec:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),tools:D.object({format:D.enum(ye).optional(),color:D.boolean().optional(),port:D.coerce.number().int().positive().optional()}).partial().optional(),status:D.record(D.string(),D.unknown()).optional(),stop:D.record(D.string(),D.unknown()).optional()}),Ce=D.object({commands:Se.default({}),tools:D.record(D.string(),xe).default({})});let j={config:be};function we(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 Te(e){let t=T.resolve(e);if(!w(t))throw Error(`Config path not found: ${t}`);return ie(t).isDirectory()?T.join(t,A):t}function Ee(e,t){let n=t.env??process.env,r=t.cwd??process.cwd(),i=t.homeDir??oe(),a=we(e);if(a)return Te(a);if(n[_e])return Te(n[_e]);let o=T.join(r,ve,A);if(w(o))return o;let s=T.join(i,ve,A);if(w(s))return s}function De(e){let t=re(e,`utf8`),n=JSON.parse(t);return Ce.parse(n)}function Oe(e,t={}){let n=Ee(e,t);return n?{configPath:n,config:De(n)}:{config:be}}function ke(e,t={}){return j=Oe(e,t),j}function M(e){return(j.config.commands??{})[e]??{}}function Ae(e){return j.config.tools?.[e]??{}}function N(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}var je=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().get(i.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 P(e){return new je(e)}const Me={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 F(e,t,n){return n?`${Me[t]}${e}${Me.reset}`:e}function I(e,t){let{format:n,color:r}=t;return n===`quiet`?``:n===`json`?Ne(e):Pe(e,r)}function Ne(e){return JSON.stringify(e,null,2)}function Pe(e,t){let n=[];e.isError&&n.push(F(`Error:`,`red`,t));for(let r of e.content)r.type===`text`?n.push(Fe(r,t)):r.type===`image`?n.push(Re(r,t)):n.push(F(`[Unknown content type: ${r.type}]`,`yellow`,t));return n.join(`
3
3
  `)}function Fe(e,t){let n=e.text;try{return Ie(JSON.parse(n),t)}catch{return n}}function Ie(e,t){if(typeof e!=`object`||!e)return String(e);let n=[];for(let[r,i]of Object.entries(e)){let e=F(r,`cyan`,t),a=Le(i,t);n.push(`${e}: ${a}`)}return n.join(`
4
4
  `)}function Le(e,t){return e===null?F(`null`,`gray`,t):e===void 0?F(`undefined`,`gray`,t):typeof e==`boolean`?F(String(e),e?`green`:`red`,t):typeof e==`number`?F(String(e),`yellow`,t):typeof e==`string`?e.startsWith(`http://`)||e.startsWith(`https://`)?F(e,`blue`,t):e:Array.isArray(e)?e.length===0?F(`[]`,`gray`,t):JSON.stringify(e,null,2):typeof e==`object`?JSON.stringify(e,null,2):String(e)}function Re(e,t){let{mimeType:n,data:r}=e;return F(`[Image: ${n}, ~${Math.round(r.length*3/4/1024)}KB base64 data]`,`magenta`,t)}function L(e,t){return F(`Error: ${e instanceof Error?e.message:e}`,`red`,t)}function ze(e,t){let n=Math.max(...e.map(e=>e.name.length)),r=[];for(let i of e){let e=F(i.name.padEnd(n),`cyan`,t);r.push(` ${e} ${i.description}`)}return r.join(`
5
- `)}function Be(e,t){let n=M(`tools`),r=N(e,`format`,t.format,n.format),i=N(e,`color`,t.color,n.color);return{port:N(e,`port`,t.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),formatterOptions:{format:r,color:i}}}const Ve=new g(`list-custom-tools`).description(`List custom tools from a tools directory`).alias(`list`).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(f)).action(async function(e,t){let{port:n,formatterOptions:r}=Be(this,t);try{let t=await P({port:Number.parseInt(n,10)}).listCustomTools(e),i=r.format===`text`?ze(t,r.color):r.format===`quiet`?``:JSON.stringify(t,null,2);i&&console.log(i)}catch(e){console.error(L(e instanceof Error?e:String(e),r.color)),process.exit(1)}}),He=new g(`exec-custom-tool`).description(`Execute a custom tool from a tools directory`).alias(`exec`).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(f)).action(async function(e,t,n,r){let{port:i,formatterOptions:a}=Be(this,r);try{let r;try{r=JSON.parse(n)}catch{console.error(L(`Invalid JSON arguments: ${n}`,a.color)),process.exit(1);return}let o=await P({port:Number.parseInt(i,10)}).executeCustomTool(e,t,r),s=I(o,a);s&&console.log(s),o.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),a.color)),process.exit(1)}});new g(`custom-tools`).description(`List and execute custom tools through the HTTP server`).addCommand(Ve).addCommand(He);const Ue=new g(`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(f)).action(async function(e,t,n){let r=M(`exec`),i=N(this,`format`,n.format,r.format),a=N(this,`color`,n.color,r.color),o=N(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(L(`Invalid JSON arguments: ${t}`,s.color)),process.exit(1)}let r=await P({port:Number.parseInt(o,10)}).execute(e,n),i=I(r,s);i&&console.log(i),r.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),s.color)),process.exit(1)}}),R=`pageId`,We=D.record(D.string(),D.unknown()),Ge=D.object({type:D.literal(`object`),properties:D.record(D.string(),D.unknown()).optional(),required:D.array(D.string()).optional(),additionalProperties:D.boolean().optional()}).passthrough(),Ke=D.object({name:D.string().min(1),description:D.string().min(1),script:D.string().min(1),suggestionActions:D.string().min(1).optional(),capabilities:We,inputSchema:Ge}),qe=D.object({tools:D.array(Ke)}),Je=process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS===`1`;function Ye(e){return JSON.stringify(e,(e,t)=>typeof t==`string`&&t.length>240?`${t.slice(0,240)}...<trimmed>`:t)}function z(e,t){if(Je){if(t){console.error(`[CustomToolService] ${e}`,t);return}console.error(`[CustomToolService] ${e}`)}}function B(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Xe(e){return B(e)&&Array.isArray(e.content)}function V(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 Ze(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 Qe(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 H(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(`-`)?$e(e,t,n):et(e,t,n)}function $e(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]=H(e,i+1,t.indent);r.push(a),i=o;continue}if(a.includes(`:`)){let[t,o]=U(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]=H(e,i+1,r.indent);s[t]=a,i=o}else s[t]=V(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]=H(e,i,t.indent);if(!B(n))throw Error(`Expected object entry in tools.yaml at line ${i+1}`);Object.assign(s,n),i=r;continue}let[r,a]=U(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]=H(e,i+1,n.indent);s[r]=a,i=o;continue}s[r]=V(a),i+=1}r.push(s);continue}r.push(V(a)),i+=1}return[r,i]}function et(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]=U(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]=H(e,i+1,t.indent);r[a]=o,i=s;continue}r[a]=V(o),i+=1}return[r,i]}function U(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 tt(e){let t=Qe(e);if(t.length===0)return{};let[n]=H(t,0,t[0].indent);return n}function nt(e){let t=e.inputSchema.properties,n=e.inputSchema.required??[];if(!t||!B(t))throw Error(`Custom tool "${e.name}" must define inputSchema.properties`);let r=t[R];if(!B(r)||r.type!==`string`)throw Error(`Custom tool "${e.name}" must define inputSchema.properties.pageId as type "string"`);if(!n.includes(R))throw Error(`Custom tool "${e.name}" must require pageId in inputSchema.required`)}async function rt(e){let t=h(await se(e,`utf8`),{mode:`strip`});return await import(`data:text/javascript;base64,${Buffer.from(t,`utf8`).toString(`base64`)}`)}function it(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 at(e,t){return typeof e.suggestionActions==`string`&&e.suggestionActions.trim().length>0?e:{...e,suggestionActions:t}}function ot(e,t){let n=e.content[0];if(n?.type===`text`&&typeof n.text==`string`)try{let r=JSON.parse(n.text);if(B(r)){let i=at(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 st(e,t,n){if(Xe(t))return n?ot(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=B(t)&&n?at(t,n):t;return{content:[{type:`text`,text:JSON.stringify(r,null,2)}]}}var ct=class{constructor(e,t,n=new c){this.pageRegistry=e,this.extensionTaskQueue=t,this.telemetry=n}resolveToolPage(e,t,r){if(r.page)return r.page;if(r.mode===`extension`&&this.extensionTaskQueue){let e=new n(this.extensionTaskQueue);return e.setTarget(t,r.browserId),e}throw Error(`Custom tool "${e}" 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:Ze({...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[R]==`string`?n[R]:void 0;return this.telemetry.runInSpan(`browse_tool.custom_tool.execute`,{attributes:{"browse_tool.tool.name":t,"browse_tool.custom_tools.directory":T.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:E.ERROR,message:`Custom tool "${t}" not found`}),Error(`Custom tool "${t}" not found`);let a=n[R];if(typeof a!=`string`||a.length===0)throw r?.setStatus({code:E.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:E.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}),z(`Executing custom tool`,{toolName:t,directory:T.resolve(e),pageId:a,browserId:o.browserId,mode:o.mode,input:Ye(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 z(`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})}z(`Custom tool execution completed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,result:Ye(l)});let u=st(t,l,i.suggestionActions),d=u.isError?u.content[0]?.text:void 0;return d&&r?.setStatus({code:E.ERROR,message:d}),u})}async loadTools(e){let t=T.resolve(e),n=T.join(t,`tools.yaml`),r=tt(await se(n,`utf8`).catch(e=>{throw Error(`Failed to read custom tool manifest at "${n}": ${e instanceof Error?e.message:String(e)}`)})),i=qe.parse(r);return Promise.all(i.tools.map(async e=>{nt(e);let n=T.resolve(t,e.script);await ce(n).catch(t=>{throw Error(`Custom tool "${e.name}" script not found at "${n}": ${t instanceof Error?t.message:String(t)}`)});let r=it(n,await rt(n));return{name:e.name,description:e.description,suggestionActions:e.suggestionActions,inputSchema:e.inputSchema,capabilities:e.capabilities,scriptPath:n,...r}}))}};function lt(e){let t=new S;return t.get(`/browsers`,async t=>{try{let n=e.get(i.BrowserService),r=e.get(i.PageRegistry),a=n.listBrowsers(),o=r.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 t.json({browsers:s,stats:c})}catch(e){return console.error(`Failed to list browsers:`,e),t.json({error:`Failed to list browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers`,async t=>{try{return await e.get(i.BrowserService).closeAll(),t.json({success:!0,message:`All browsers closed`})}catch(e){return console.error(`Failed to close all browsers:`,e),t.json({error:`Failed to close all browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.BrowserService);return r.getBrowser(n)?(await r.closeBrowser(n),t.json({success:!0,message:`Browser "${n}" closed`})):t.json({error:`Browser "${n}" not found`},404)}catch(e){return console.error(`Failed to close browser:`,e),t.json({error:`Failed to close browser`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/pages/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.PageRegistry).get(n);return r?r.page?(await r.page.close(),t.json({success:!0,message:`Page "${n}" closed`})):t.json({error:`Page "${n}" is in extension mode and cannot be closed via API`},400):t.json({error:`Page "${n}" not found`},404)}catch(e){return console.error(`Failed to close page:`,e),t.json({error:`Failed to close page`,message:e instanceof Error?e.message:String(e)},500)}}),t}function ut(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 dt(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 ft(e){return dt(new Date().getTime()-e.getTime())}const W=`table-container`,G=`stat-card`,pt=`browser-row`,mt=`page-row`,K=`status-active`,ht=`url-cell`,q=`timestamp-cell`,J=`actions-cell`,Y=`kill-btn`,gt=`empty-state`;function _t({browsers:e}){return e.length===0?O(`div`,{class:W,children:k(`div`,{class:gt,children:[O(`h3`,{children:`No Active Browsers`}),O(`p`,{children:`Launch a browser using MCP tools to see it here.`})]})}):O(`div`,{class:W,children:k(`table`,{class:`browser-table`,children:[O(`thead`,{children:k(`tr`,{children:[O(`th`,{children:`ID`}),O(`th`,{children:`Status`}),O(`th`,{children:`URL / Title`}),O(`th`,{children:`Created`}),O(`th`,{children:`Age`}),O(`th`,{children:`Actions`})]})}),O(`tbody`,{id:`browser-table-body`,children:e.map(e=>k(de,{children:[k(`tr`,{class:pt,children:[O(`td`,{children:e.id}),O(`td`,{children:k(`span`,{class:K,children:[e.pages.length,` page`,e.pages.length===1?``:`s`]})}),O(`td`,{children:e.profileName||`Default Profile`}),O(`td`,{class:q,children:ut(e.createdAt)}),O(`td`,{class:q,children:ft(e.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killBrowser('${e.id}')`,children:`Kill`})})]}),e.pages.map(t=>k(`tr`,{class:mt,children:[k(`td`,{children:[t.id,e.currentPageId===t.id&&` (active)`]}),O(`td`,{children:O(`span`,{class:K,children:`Open`})}),O(`td`,{class:ht,title:t.url,children:t.title||t.url||`about:blank`}),O(`td`,{class:q,children:ut(t.createdAt)}),O(`td`,{class:q,children:ft(t.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killPage('${t.id}')`,children:`Close`})})]}))]}))})]})})}function vt({stats:e}){return k(`div`,{class:`stats-container`,children:[k(`div`,{class:G,children:[O(`h3`,{children:`Active Browsers`}),O(`div`,{class:`value`,children:e.totalBrowsers})]}),k(`div`,{class:G,children:[O(`h3`,{children:`Active Pages`}),O(`div`,{class:`value`,children:e.totalPages})]})]})}function yt({browsers:e,stats:t}){return k(`div`,{class:`dashboard-container`,children:[k(`div`,{class:`dashboard-header`,children:[k(`div`,{children:[O(`h1`,{children:`Playwright MCP Dashboard`}),O(`p`,{children:`Browser automation management (auto-refresh every 3 seconds)`})]}),k(`div`,{children:[O(`button`,{id:`refresh-btn`,class:`btn refresh-btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`}),O(`button`,{id:`kill-all-btn`,class:`btn btn-danger`,onclick:`dashboard.killAllBrowsers()`,children:`Kill All Browsers`})]})]}),O(vt,{stats:t}),O(_t,{browsers:e}),O(`script`,{children:ue(`
5
+ `)}function Be(e,t){let n=M(`tools`),r=N(e,`format`,t.format,n.format),i=N(e,`color`,t.color,n.color);return{port:N(e,`port`,t.port,n.port===void 0?void 0:String(n.port),process.env.PLAYWRIGHT_PORT),formatterOptions:{format:r,color:i}}}const Ve=new g(`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(f)).action(async function(e,t){let{port:n,formatterOptions:r}=Be(this,t);try{let t=await P({port:Number.parseInt(n,10)}).listCustomTools(e),i=r.format===`text`?ze(t,r.color):r.format===`quiet`?``:JSON.stringify(t,null,2);i&&console.log(i)}catch(e){console.error(L(e instanceof Error?e:String(e),r.color)),process.exit(1)}}),He=new g(`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(f)).action(async function(e,t,n,r){let{port:i,formatterOptions:a}=Be(this,r);try{let r;try{r=JSON.parse(n)}catch{console.error(L(`Invalid JSON arguments: ${n}`,a.color)),process.exit(1);return}let o=await P({port:Number.parseInt(i,10)}).executeCustomTool(e,t,r),s=I(o,a);s&&console.log(s),o.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),a.color)),process.exit(1)}});new g(`custom-tools`).description(`List and execute custom tools through the HTTP server`).addCommand(Ve).addCommand(He);const Ue=new g(`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(f)).action(async function(e,t,n){let r=M(`exec`),i=N(this,`format`,n.format,r.format),a=N(this,`color`,n.color,r.color),o=N(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(L(`Invalid JSON arguments: ${t}`,s.color)),process.exit(1)}let r=await P({port:Number.parseInt(o,10)}).execute(e,n),i=I(r,s);i&&console.log(i),r.isError&&process.exit(1)}catch(e){console.error(L(e instanceof Error?e:String(e),s.color)),process.exit(1)}}),R=`pageId`,We=D.record(D.string(),D.unknown()),Ge=D.object({type:D.literal(`object`),properties:D.record(D.string(),D.unknown()).optional(),required:D.array(D.string()).optional(),additionalProperties:D.boolean().optional()}).passthrough(),Ke=D.object({name:D.string().min(1),description:D.string().min(1),script:D.string().min(1),suggestionActions:D.string().min(1).optional(),capabilities:We,inputSchema:Ge}),qe=D.object({tools:D.array(Ke)}),Je=process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS===`1`;function Ye(e){return JSON.stringify(e,(e,t)=>typeof t==`string`&&t.length>240?`${t.slice(0,240)}...<trimmed>`:t)}function z(e,t){if(Je){if(t){console.error(`[CustomToolService] ${e}`,t);return}console.error(`[CustomToolService] ${e}`)}}function B(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Xe(e){return B(e)&&Array.isArray(e.content)}function V(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 Ze(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 Qe(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 H(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(`-`)?$e(e,t,n):et(e,t,n)}function $e(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]=H(e,i+1,t.indent);r.push(a),i=o;continue}if(a.includes(`:`)){let[t,o]=U(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]=H(e,i+1,r.indent);s[t]=a,i=o}else s[t]=V(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]=H(e,i,t.indent);if(!B(n))throw Error(`Expected object entry in tools.yaml at line ${i+1}`);Object.assign(s,n),i=r;continue}let[r,a]=U(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]=H(e,i+1,n.indent);s[r]=a,i=o;continue}s[r]=V(a),i+=1}r.push(s);continue}r.push(V(a)),i+=1}return[r,i]}function et(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]=U(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]=H(e,i+1,t.indent);r[a]=o,i=s;continue}r[a]=V(o),i+=1}return[r,i]}function U(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 tt(e){let t=Qe(e);if(t.length===0)return{};let[n]=H(t,0,t[0].indent);return n}function nt(e){let t=e.inputSchema.properties,n=e.inputSchema.required??[];if(!t||!B(t))throw Error(`Custom tool "${e.name}" must define inputSchema.properties`);let r=t[R];if(!B(r)||r.type!==`string`)throw Error(`Custom tool "${e.name}" must define inputSchema.properties.pageId as type "string"`);if(!n.includes(R))throw Error(`Custom tool "${e.name}" must require pageId in inputSchema.required`)}async function rt(e){let t=h(await se(e,`utf8`),{mode:`strip`});return await import(`data:text/javascript;base64,${Buffer.from(t,`utf8`).toString(`base64`)}`)}function it(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 at(e,t){return typeof e.suggestionActions==`string`&&e.suggestionActions.trim().length>0?e:{...e,suggestionActions:t}}function ot(e,t){let n=e.content[0];if(n?.type===`text`&&typeof n.text==`string`)try{let r=JSON.parse(n.text);if(B(r)){let i=at(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 st(e,t,n){if(Xe(t))return n?ot(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=B(t)&&n?at(t,n):t;return{content:[{type:`text`,text:JSON.stringify(r,null,2)}]}}var ct=class{constructor(e,t,n=new c){this.pageRegistry=e,this.extensionTaskQueue=t,this.telemetry=n}resolveToolPage(e,t,r){if(r.page)return r.page;if(r.mode===`extension`&&this.extensionTaskQueue){let e=new n(this.extensionTaskQueue);return e.setTarget(t,r.browserId),e}throw Error(`Custom tool "${e}" 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:Ze({...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[R]==`string`?n[R]:void 0;return this.telemetry.runInSpan(`browse_tool.custom_tool.execute`,{attributes:{"browse_tool.tool.name":t,"browse_tool.custom_tools.directory":T.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:E.ERROR,message:`Custom tool "${t}" not found`}),Error(`Custom tool "${t}" not found`);let a=n[R];if(typeof a!=`string`||a.length===0)throw r?.setStatus({code:E.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:E.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}),z(`Executing custom tool`,{toolName:t,directory:T.resolve(e),pageId:a,browserId:o.browserId,mode:o.mode,input:Ye(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 z(`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})}z(`Custom tool execution completed`,{toolName:t,pageId:a,browserId:o.browserId,mode:o.mode,result:Ye(l)});let u=st(t,l,i.suggestionActions),d=u.isError?u.content[0]?.text:void 0;return d&&r?.setStatus({code:E.ERROR,message:d}),u})}async loadTools(e){let t=T.resolve(e),n=T.join(t,`tools.yaml`),r=tt(await se(n,`utf8`).catch(e=>{throw Error(`Failed to read custom tool manifest at "${n}": ${e instanceof Error?e.message:String(e)}`)})),i=qe.parse(r);return Promise.all(i.tools.map(async e=>{nt(e);let n=T.resolve(t,e.script);await ce(n).catch(t=>{throw Error(`Custom tool "${e.name}" script not found at "${n}": ${t instanceof Error?t.message:String(t)}`)});let r=it(n,await rt(n));return{name:e.name,description:e.description,suggestionActions:e.suggestionActions,inputSchema:e.inputSchema,capabilities:e.capabilities,scriptPath:n,...r}}))}};function lt(e){let t=new S;return t.get(`/browsers`,async t=>{try{let n=e.get(i.BrowserService),r=e.get(i.PageRegistry),a=n.listBrowsers(),o=r.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 t.json({browsers:s,stats:c})}catch(e){return console.error(`Failed to list browsers:`,e),t.json({error:`Failed to list browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers`,async t=>{try{return await e.get(i.BrowserService).closeAll(),t.json({success:!0,message:`All browsers closed`})}catch(e){return console.error(`Failed to close all browsers:`,e),t.json({error:`Failed to close all browsers`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/browsers/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.BrowserService);return r.getBrowser(n)?(await r.closeBrowser(n),t.json({success:!0,message:`Browser "${n}" closed`})):t.json({error:`Browser "${n}" not found`},404)}catch(e){return console.error(`Failed to close browser:`,e),t.json({error:`Failed to close browser`,message:e instanceof Error?e.message:String(e)},500)}}),t.delete(`/pages/:id`,async t=>{try{let n=t.req.param(`id`),r=e.get(i.PageRegistry).get(n);return r?r.page?(await r.page.close(),t.json({success:!0,message:`Page "${n}" closed`})):t.json({error:`Page "${n}" is in extension mode and cannot be closed via API`},400):t.json({error:`Page "${n}" not found`},404)}catch(e){return console.error(`Failed to close page:`,e),t.json({error:`Failed to close page`,message:e instanceof Error?e.message:String(e)},500)}}),t}function ut(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 dt(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 ft(e){return dt(new Date().getTime()-e.getTime())}const W=`table-container`,G=`stat-card`,pt=`browser-row`,mt=`page-row`,K=`status-active`,ht=`url-cell`,q=`timestamp-cell`,J=`actions-cell`,Y=`kill-btn`,gt=`empty-state`;function _t({browsers:e}){return e.length===0?O(`div`,{class:W,children:k(`div`,{class:gt,children:[O(`h3`,{children:`No Active Browsers`}),O(`p`,{children:`Launch a browser using MCP tools to see it here.`})]})}):O(`div`,{class:W,children:k(`table`,{class:`browser-table`,children:[O(`thead`,{children:k(`tr`,{children:[O(`th`,{children:`ID`}),O(`th`,{children:`Status`}),O(`th`,{children:`URL / Title`}),O(`th`,{children:`Created`}),O(`th`,{children:`Age`}),O(`th`,{children:`Actions`})]})}),O(`tbody`,{id:`browser-table-body`,children:e.map(e=>k(de,{children:[k(`tr`,{class:pt,children:[O(`td`,{children:e.id}),O(`td`,{children:k(`span`,{class:K,children:[e.pages.length,` page`,e.pages.length===1?``:`s`]})}),O(`td`,{children:e.profileName||`Default Profile`}),O(`td`,{class:q,children:ut(e.createdAt)}),O(`td`,{class:q,children:ft(e.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killBrowser('${e.id}')`,children:`Kill`})})]}),e.pages.map(t=>k(`tr`,{class:mt,children:[k(`td`,{children:[t.id,e.currentPageId===t.id&&` (active)`]}),O(`td`,{children:O(`span`,{class:K,children:`Open`})}),O(`td`,{class:ht,title:t.url,children:t.title||t.url||`about:blank`}),O(`td`,{class:q,children:ut(t.createdAt)}),O(`td`,{class:q,children:ft(t.createdAt)}),O(`td`,{class:J,children:O(`button`,{class:Y,onclick:`dashboard.killPage('${t.id}')`,children:`Close`})})]}))]}))})]})})}function vt({stats:e}){return k(`div`,{class:`stats-container`,children:[k(`div`,{class:G,children:[O(`h3`,{children:`Active Browsers`}),O(`div`,{class:`value`,children:e.totalBrowsers})]}),k(`div`,{class:G,children:[O(`h3`,{children:`Active Pages`}),O(`div`,{class:`value`,children:e.totalPages})]})]})}function yt({browsers:e,stats:t}){return k(`div`,{class:`dashboard-container`,children:[k(`div`,{class:`dashboard-header`,children:[k(`div`,{children:[O(`h1`,{children:`Playwright MCP Dashboard`}),O(`p`,{children:`Browser automation management (auto-refresh every 3 seconds)`})]}),k(`div`,{children:[O(`button`,{id:`refresh-btn`,class:`btn refresh-btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`}),O(`button`,{id:`kill-all-btn`,class:`btn btn-danger`,onclick:`dashboard.killAllBrowsers()`,children:`Kill All Browsers`})]})]}),O(vt,{stats:t}),O(_t,{browsers:e}),O(`script`,{children:ue(`
6
6
  class DashboardManager {
7
7
  constructor() {
8
8
  this.autoRefreshInterval = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agimon-ai/browse-tool",
3
3
  "description": "MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "license": "BUSL-1.1",
6
6
  "keywords": [
7
7
  "mcp",