@agimon-ai/browse-tool 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.mjs","names":["container","result: ExtensionTaskResult","response: StatusResponse","toolDefinitions: ToolDefinition[]","container","DEFAULT_CONFIG: BrowseToolConfig","runtimeContext: CliRuntimeContext","lines: string[]","formatterOptions: FormatterOptions","args: Record<string, unknown>","container","browsers: BrowserResponse[]","stats: StatsResponse","styles.tableContainer","styles.emptyState","styles.table","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","styles.statsContainer","styles.statCard","styles.container","styles.header","styles.button","styles.refreshButton","styles.buttonDanger","styles.statCard","styles.tableContainer","styles.emptyState","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","container","browsers: BrowserData[]","container","response: HealthResponse","connectionId: string | null","container","resolvedOptions: HttpServeOptions","container","browserId: string","pageId: string","TOOL_TAGS: Record<string, ToolTag[]>","resolve","lastError: Error | undefined","sessionToolDefinition: ToolDefinition","cachedTools: ToolDefinition[]","failedIds: string[]","firstCause: Error | undefined","cleanupError: Error | undefined","resolvedOptions: McpServeOptions","formatterOptions: FormatterOptions","args: Record<string, unknown>","SESSION_TOOL_DEFINITION: ToolDefinition","packageJson.version"],"sources":["../package.json","../src/server/extension-routes.ts","../src/commands/chrome-serve.ts","../src/config.ts","../src/utils/httpClient.ts","../src/utils/outputFormatter.ts","../src/commands/exec.ts","../src/server/dashboard/routes/api.ts","../src/server/dashboard/utils/formatters.ts","../src/server/dashboard/components/styles.ts","../src/server/dashboard/components/BrowserTable.tsx","../src/server/dashboard/components/StatsHeader.tsx","../src/server/dashboard/components/Dashboard.tsx","../src/server/dashboard/components/Layout.tsx","../src/server/dashboard/routes/dashboard.tsx","../src/server/http.ts","../src/server/websocket-routes.ts","../src/commands/http-serve.ts","../src/constants/tool-tags.ts","../src/server/proxy.ts","../src/commands/mcp-serve.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/utils/cliArgs.ts","../src/utils/commandBuilder.ts","../src/utils/toolCommands.ts","../src/cli.ts"],"sourcesContent":["{\n \"name\": \"@agimon-ai/browse-tool\",\n \"description\": \"MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support\",\n \"version\": \"0.2.0\",\n \"license\": \"BUSL-1.1\",\n \"keywords\": [\"mcp\", \"model-context-protocol\", \"playwright\", \"browser-automation\", \"typescript\"],\n \"bin\": {\n \"browse-tool\": \"./dist/cli.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"types\": \"./dist/index.d.cts\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\"dist\", \"README.md\"],\n \"scripts\": {\n \"dev\": \"tsx src/cli.ts mcp-serve\",\n \"build\": \"tsdown && pnpm build:extension\",\n \"build:extension\": \"vite build --config vite.config.extension.ts\",\n \"dev:extension\": \"vite build --config vite.config.extension.ts --watch --mode development\",\n \"test\": \"vitest --run\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\"\n },\n \"dependencies\": {\n \"@agimon-ai/foundation-port-registry\": \"workspace:*\",\n \"@hono/node-server\": \"1.19.9\",\n \"@hono/node-ws\": \"^1.3.0\",\n \"@modelcontextprotocol/sdk\": \"1.19.1\",\n \"@playwright/test\": \"1.57.0\",\n \"chalk\": \"5.6.2\",\n \"commander\": \"14.0.1\",\n \"hono\": \"4.11.4\",\n \"inversify\": \"7.0.1\",\n \"playwright\": \"1.57.0\",\n \"reflect-metadata\": \"0.2.2\",\n \"ws\": \"8.18.0\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@types/chrome\": \"0.0.322\",\n \"@types/node\": \"^22.0.0\",\n \"@types/ws\": \"8.5.13\",\n \"tsdown\": \"0.16.1\",\n \"typescript\": \"5.9.3\",\n \"vite\": \"7.0.5\",\n \"vite-plugin-static-copy\": \"^3.1.5\",\n \"vitest\": \"4.0.18\"\n },\n \"type\": \"module\",\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org/\",\n \"access\": \"public\"\n },\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./cli\": {\n \"import\": \"./dist/cli.mjs\",\n \"require\": \"./dist/cli.cjs\"\n },\n \"./stubs/playwright-test\": {\n \"import\": \"./dist/stubs/playwright-test.mjs\",\n \"require\": \"./dist/stubs/playwright-test.cjs\"\n },\n \"./package.json\": \"./package.json\"\n }\n}\n","/**\n * Extension HTTP Routes\n *\n * DESIGN PATTERNS:\n * - Hono router for HTTP routing\n * - RESTful API endpoints for extension communication\n * - Dependency injection with Container\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing\n * - Keep route handlers thin, delegate to services\n * - Return appropriate HTTP status codes\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type {\n HandoffRequest,\n IExtensionSessionRegistry,\n SessionHeartbeatRequest,\n SessionRegistrationRequest,\n} from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue, ExtensionTaskResult } from '../services/ExtensionTaskQueue.js';\n\n/**\n * Task request body from extension polling\n */\ninterface TaskPollResponse {\n task?: {\n id: string;\n tool: string;\n arguments: Record<string, unknown>;\n };\n}\n\n/**\n * Result submission body from extension\n */\ninterface ResultSubmitRequest {\n taskId: string;\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\n/**\n * Status response\n */\ninterface StatusResponse {\n connected: boolean;\n lastPollAt?: string;\n lastResultAt?: string;\n pendingTasks: number;\n queueSize: number;\n}\n\n/**\n * Create extension routes for Chrome extension communication\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono router\n */\nexport function createExtensionRoutes(container: Container): Hono {\n const router = new Hono();\n\n /**\n * GET /extension/tasks\n * Extension polls for next task to execute\n */\n router.get('/tasks', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const task = taskQueue.getNextTask();\n\n if (!task) {\n return c.json<TaskPollResponse>({});\n }\n\n return c.json<TaskPollResponse>({\n task: {\n id: task.id,\n tool: task.tool,\n arguments: task.arguments,\n },\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/result\n * Extension submits task execution result\n */\n router.post('/result', async (c) => {\n try {\n const body = (await c.req.json()) as ResultSubmitRequest;\n\n if (!body.taskId) {\n return c.json(\n {\n success: false,\n error: 'Missing taskId in request body',\n },\n 400,\n );\n }\n\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n\n const result: ExtensionTaskResult = {\n taskId: body.taskId,\n success: body.success,\n result: body.result,\n error: body.error,\n };\n\n const found = taskQueue.submitResult(result);\n\n if (!found) {\n return c.json(\n {\n success: false,\n error: `Task ${body.taskId} not found or already completed`,\n },\n 404,\n );\n }\n\n return c.json({ success: true });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/status\n * Connection health check and status\n */\n router.get('/status', (c) => {\n try {\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const status = taskQueue.getConnectionStatus();\n\n const response: StatusResponse = {\n connected: status.connected,\n lastPollAt: status.lastPollAt?.toISOString(),\n lastResultAt: status.lastResultAt?.toISOString(),\n pendingTasks: status.pendingTasks,\n queueSize: taskQueue.queueSize,\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n connected: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/register\n * Extension registers a stealth browser session\n */\n router.post('/register', async (c) => {\n try {\n const body = (await c.req.json()) as SessionRegistrationRequest;\n\n if (!body.browserId) {\n return c.json(\n {\n success: false,\n error: 'Missing browserId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.register(body);\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n browserId: session.browserId,\n controlMode: session.controlMode,\n createdAt: session.createdAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/heartbeat\n * Extension sends heartbeat to keep session alive\n */\n router.post('/heartbeat', async (c) => {\n try {\n const body = (await c.req.json()) as SessionHeartbeatRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.heartbeat(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n lastHeartbeatAt: session.lastHeartbeatAt.toISOString(),\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff\n * Request handoff from spec to AI control\n */\n router.post('/handoff', async (c) => {\n try {\n const body = (await c.req.json()) as HandoffRequest;\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.requestHandoff(body);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff requested. AI will take control when ready.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * POST /extension/handoff/acknowledge\n * AI acknowledges handoff and takes control\n */\n router.post('/handoff/acknowledge', async (c) => {\n try {\n const body = (await c.req.json()) as { sessionId: string };\n\n if (!body.sessionId) {\n return c.json(\n {\n success: false,\n error: 'Missing sessionId in request body',\n },\n 400,\n );\n }\n\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const session = sessionRegistry.acknowledgeHandoff(body.sessionId);\n\n if (!session) {\n return c.json(\n {\n success: false,\n error: `Session ${body.sessionId} not found or no handoff pending`,\n },\n 404,\n );\n }\n\n return c.json({\n success: true,\n message: 'Handoff acknowledged. AI now has control.',\n session: {\n id: session.id,\n controlMode: session.controlMode,\n handoffRequested: session.handoffRequested,\n },\n });\n } catch (error) {\n return c.json(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * GET /extension/sessions\n * List all active extension sessions\n */\n router.get('/sessions', (c) => {\n try {\n const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n const sessions = sessionRegistry.listSessions();\n\n return c.json({\n sessions: sessions.map((s) => ({\n id: s.id,\n browserId: s.browserId,\n tabId: s.tabId,\n currentUrl: s.currentUrl,\n controlMode: s.controlMode,\n activeSpecPath: s.activeSpecPath,\n handoffRequested: s.handoffRequested,\n createdAt: s.createdAt.toISOString(),\n lastHeartbeatAt: s.lastHeartbeatAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return router;\n}\n","/**\n * Chrome Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - HTTP server pattern for Chrome extension polling\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n */\n\nimport { serve } from '@hono/node-server';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { Command } from 'commander';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createExtensionRoutes } from '../server/extension-routes.js';\nimport { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { ExtensionToolDelegator } from '../services/ExtensionToolDelegator.js';\nimport type { ToolDefinition } from '../types/index.js';\n\nexport interface ChromeServeOptions {\n port: number;\n verbose: boolean;\n waitForExtension: boolean;\n}\n\n/**\n * Create extension-specific container module\n */\nfunction createExtensionModule(): ContainerModule {\n return new ContainerModule((options: ContainerModuleLoadOptions) => {\n options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n });\n}\n\n/**\n * Create MCP server that delegates to Chrome extension\n */\nfunction createExtensionMcpServer(delegator: ExtensionToolDelegator): Server {\n const server = new Server(\n {\n name: 'browse-tool-chrome',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n const supportedTools = delegator.getSupportedTools();\n\n const toolDefinitions: ToolDefinition[] = supportedTools.map((name) => ({\n name,\n description: `Browser automation tool (Chrome extension mode): ${name}`,\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: true,\n },\n }));\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: toolDefinitions };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n return await delegator.executeTool(name, args || {});\n });\n\n return server;\n}\n\nexport const chromeServeCommand = new Command('chrome-serve')\n .description(\n '[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation',\n )\n .option('-p, --port <port>', 'HTTP server port for extension polling', '3200')\n .option('-v, --verbose', 'Enable verbose output', false)\n .option('--wait-for-extension', 'Wait for extension to connect before accepting MCP requests', false)\n .action(async (options: ChromeServeOptions) => {\n // Deprecation warning\n console.error('');\n console.error('╔════════════════════════════════════════════════════════════════╗');\n console.error('║ DEPRECATED: chrome-serve is deprecated. ║');\n console.error('║ Use mcp-serve instead - extension routes are now ║');\n console.error('║ automatically available in the HTTP server at /extension/*. ║');\n console.error('╚════════════════════════════════════════════════════════════════╝');\n console.error('');\n\n try {\n const port = typeof options.port === 'string' ? Number.parseInt(options.port, 10) : options.port;\n\n if (options.verbose) {\n console.error('Chrome Extension MCP Server starting...');\n console.error(` HTTP Port: ${port}`);\n console.error(` Wait for extension: ${options.waitForExtension}`);\n }\n\n // Create container with extension services\n const container = new Container({ defaultScope: 'Singleton' });\n container.load(createExtensionModule());\n\n // Get services\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const delegator = container.get<ExtensionToolDelegator>(PLAYWRIGHT_TYPES.ExtensionToolDelegator);\n\n // Create HTTP server for extension polling\n const app = new Hono();\n\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept'],\n }),\n );\n\n // Mount extension routes\n const extensionRoutes = createExtensionRoutes(container);\n app.route('/extension', extensionRoutes);\n\n // Health check\n app.get('/health', (c) => {\n const status = taskQueue.getConnectionStatus();\n return c.json({\n status: 'healthy',\n service: 'browse-tool-chrome',\n extension: status,\n });\n });\n\n // Start HTTP server\n const httpServer = serve({\n fetch: app.fetch,\n port,\n });\n\n // Handle server errors (e.g., port in use)\n httpServer.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n console.error(`Error [PORT_IN_USE]: Port ${port} is already in use.`);\n console.error('Recovery: Try a different port with --port <port>');\n } else {\n console.error(`Error [SERVER_ERROR]: HTTP server error: ${error.message}`);\n }\n process.exit(1);\n });\n\n console.error(`HTTP server started on port ${port}`);\n console.error('Waiting for Chrome extension to connect...');\n console.error(`Extension should poll: http://localhost:${port}/extension/tasks`);\n\n // Wait for extension if requested\n if (options.waitForExtension) {\n console.error('Waiting for extension connection before starting MCP...');\n await new Promise<void>((resolve) => {\n const checkInterval = setInterval(() => {\n const status = taskQueue.getConnectionStatus();\n if (status.connected) {\n clearInterval(checkInterval);\n console.error('Extension connected!');\n resolve();\n }\n }, 500);\n });\n }\n\n // Create MCP server with extension delegation\n const mcpServer = createExtensionMcpServer(delegator);\n\n // Start stdio transport\n const transport = new StdioServerTransport();\n await mcpServer.connect(transport);\n\n console.error('Chrome extension MCP server started on stdio');\n\n const shutdown = async (signal: string) => {\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n try {\n taskQueue.clearAllTasks('Server shutting down');\n await transport.close();\n httpServer.close();\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${errorMessage}`);\n console.error('Recovery: Check that the port is available and try again.');\n process.exit(1);\n }\n });\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport { z } from 'zod';\n\nconst CONFIG_ENV_VAR = 'BROWSE_TOOL_CONFIG';\nconst CONFIG_DIR_NAME = '.browse-tool';\nconst CONFIG_FILE_NAME = 'config.json';\nconst OUTPUT_FORMATS = ['json', 'text', 'quiet'] as const;\nconst DEFAULT_CONFIG: BrowseToolConfig = {\n commands: {},\n tools: {},\n};\n\nconst toolDefaultsSchema = z.record(z.string(), z.unknown());\n\nconst commandDefaultsSchema = z.object({\n mcpServe: z\n .object({\n type: z.string().optional(),\n browser: z.string().optional(),\n headless: z.boolean().optional(),\n profile: z.string().optional(),\n mode: z.string().optional(),\n host: z.string().optional(),\n tags: z.string().optional(),\n exclude: z.string().optional(),\n registryPath: z.string().optional(),\n registryDir: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n })\n .partial()\n .optional(),\n httpServe: z\n .object({\n port: z.coerce.number().int().positive().optional(),\n headless: z.boolean().optional(),\n idleTimeout: z.coerce.number().positive().optional(),\n host: z.string().optional(),\n registryDir: z.string().optional(),\n registryPath: z.string().optional(),\n pidsDir: z.string().optional(),\n profilesDir: z.string().optional(),\n })\n .partial()\n .optional(),\n exec: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n tools: z\n .object({\n format: z.enum(OUTPUT_FORMATS).optional(),\n color: z.boolean().optional(),\n port: z.coerce.number().int().positive().optional(),\n })\n .partial()\n .optional(),\n status: z.record(z.string(), z.unknown()).optional(),\n stop: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst browseToolConfigSchema = z.object({\n commands: commandDefaultsSchema.default({}),\n tools: z.record(z.string(), toolDefaultsSchema).default({}),\n});\n\nexport type BrowseToolConfig = z.infer<typeof browseToolConfigSchema>;\n\nexport interface CliRuntimeContext {\n config: BrowseToolConfig;\n configPath?: string;\n}\n\nexport interface CliRuntimeOptions {\n cwd?: string;\n env?: NodeJS.ProcessEnv;\n homeDir?: string;\n}\n\nlet runtimeContext: CliRuntimeContext = {\n config: DEFAULT_CONFIG,\n};\n\nfunction extractExplicitConfigPath(argv: string[]): string | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === '--config') {\n return argv[i + 1];\n }\n if (arg.startsWith('--config=')) {\n return arg.slice('--config='.length);\n }\n }\n return undefined;\n}\n\nfunction resolveConfigCandidatePath(candidatePath: string): string {\n const resolvedPath = path.resolve(candidatePath);\n if (!existsSync(resolvedPath)) {\n throw new Error(`Config path not found: ${resolvedPath}`);\n }\n\n if (statSync(resolvedPath).isDirectory()) {\n return path.join(resolvedPath, CONFIG_FILE_NAME);\n }\n\n return resolvedPath;\n}\n\nfunction findConfigPath(argv: string[], options: CliRuntimeOptions): string | undefined {\n const env = options.env ?? process.env;\n const cwd = options.cwd ?? process.cwd();\n const homeDir = options.homeDir ?? homedir();\n\n const explicitPath = extractExplicitConfigPath(argv);\n if (explicitPath) {\n return resolveConfigCandidatePath(explicitPath);\n }\n\n if (env[CONFIG_ENV_VAR]) {\n return resolveConfigCandidatePath(env[CONFIG_ENV_VAR] as string);\n }\n\n const localConfigPath = path.join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(localConfigPath)) {\n return localConfigPath;\n }\n\n const homeConfigPath = path.join(homeDir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n if (existsSync(homeConfigPath)) {\n return homeConfigPath;\n }\n\n return undefined;\n}\n\nfunction readConfigFile(configPath: string): BrowseToolConfig {\n const content = readFileSync(configPath, 'utf8');\n const parsed = JSON.parse(content) as unknown;\n return browseToolConfigSchema.parse(parsed);\n}\n\nexport function loadCliRuntimeContext(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n const configPath = findConfigPath(argv, options);\n if (!configPath) {\n return { config: DEFAULT_CONFIG };\n }\n\n return {\n configPath,\n config: readConfigFile(configPath),\n };\n}\n\nexport function initializeCliRuntime(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n runtimeContext = loadCliRuntimeContext(argv, options);\n return runtimeContext;\n}\n\nexport function getCliRuntimeContext(): CliRuntimeContext {\n return runtimeContext;\n}\n\nexport function setCliRuntimeContextForTesting(context: CliRuntimeContext): void {\n runtimeContext = context;\n}\n\nexport function getCommandConfig<T extends Record<string, unknown>>(\n commandName: keyof BrowseToolConfig['commands'],\n): T {\n const commands = runtimeContext.config.commands ?? {};\n return (commands[commandName] ?? {}) as T;\n}\n\nexport function getToolConfig(toolName: string): Record<string, unknown> {\n return runtimeContext.config.tools?.[toolName] ?? {};\n}\n\nexport function resolveConfiguredOption<T>(\n command: Command,\n optionName: string,\n currentValue: T,\n configValue?: T,\n envValue?: T,\n): T {\n const source = command.getOptionValueSourceWithGlobals(optionName);\n if (source !== undefined && source !== 'default' && source !== 'implied') {\n return currentValue;\n }\n\n if (envValue !== undefined) {\n return envValue;\n }\n\n if (configValue !== undefined) {\n return configValue;\n }\n\n return currentValue;\n}\n","/**\n * HTTP Client Utility for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Service pattern for HTTP communication\n * - Dependency injection via container\n * - Error handling with descriptive messages\n *\n * CODING STANDARDS:\n * - Use async/await for all HTTP operations\n * - Return typed responses\n * - Handle connection errors gracefully\n *\n * AVOID:\n * - Hardcoded URLs (use server discovery)\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createMcpContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/**\n * Response from /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n}\n\n/**\n * Browser info response from /browsers\n */\nexport interface BrowserInfo {\n id: string;\n profileName?: string;\n pageIds: string[];\n currentPageId?: string;\n createdAt: string;\n}\n\ninterface BrowsersResponse {\n browsers: BrowserInfo[];\n error?: string;\n}\n\n/**\n * Response from /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\n/**\n * Client options\n */\nexport interface ToolClientOptions {\n /** Port to use for HTTP server */\n port?: number;\n /** Timeout for HTTP requests in milliseconds */\n timeout?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60000;\n\n/**\n * HTTP client for executing tools via the browse-tool HTTP server.\n * Handles server discovery/startup and provides typed methods for tool operations.\n */\nexport class ToolClient {\n private readonly port: number;\n private readonly timeout: number;\n private serverPort: number | null = null;\n\n constructor(options: ToolClientOptions = {}) {\n this.port = options.port ?? 3200;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Ensure HTTP server is running and return its port\n */\n private async ensureServer(): Promise<number> {\n if (this.serverPort !== null) {\n return this.serverPort;\n }\n\n const container = createMcpContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n const status = await httpServerManager.ensureRunning(this.port);\n\n if (!status.running || !status.port) {\n throw new Error(status.error ?? 'Failed to start HTTP server. Try running \"browse-tool http-serve\" manually.');\n }\n\n this.serverPort = status.port;\n return status.port;\n }\n\n /**\n * Get base URL for HTTP server\n */\n private async getBaseUrl(): Promise<string> {\n const port = await this.ensureServer();\n return `http://localhost:${port}`;\n }\n\n /**\n * List all available tools from the HTTP server\n */\n async listTools(): Promise<ToolDefinition[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/tools`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n return data.tools;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`);\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * List active browsers from the HTTP server\n */\n async listBrowsers(): Promise<BrowserInfo[]> {\n const baseUrl = await this.getBaseUrl();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${baseUrl}/browsers`, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as BrowsersResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.browsers;\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`);\n }\n throw this.wrapConnectionError(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Execute a tool with the given arguments\n */\n async execute(tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n const baseUrl = await this.getBaseUrl();\n // run_spec can execute long-running suites. Do not enforce client-side request timeout.\n const timeoutMs = tool === 'run_spec' ? undefined : this.timeout;\n const controller = new AbortController();\n const timeoutId = timeoutMs !== undefined ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n\n try {\n const response = await fetch(`${baseUrl}/execute`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({\n tool,\n arguments: args,\n }),\n signal: timeoutMs !== undefined ? controller.signal : undefined,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n return {\n content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n isError: true,\n };\n }\n\n return (\n data.result ?? {\n content: [{ type: 'text', text: 'No result returned' }],\n }\n );\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.timeout}ms`);\n }\n throw this.wrapConnectionError(error);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n\n /**\n * Wrap connection errors with helpful recovery messages\n */\n private wrapConnectionError(error: unknown): Error {\n if (error instanceof Error) {\n if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n return new Error(\n `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: ${error.message}`,\n );\n }\n return error;\n }\n return new Error(String(error));\n }\n}\n\n/**\n * Create a new ToolClient instance\n */\nexport function createToolClient(options?: ToolClientOptions): ToolClient {\n return new ToolClient(options);\n}\n","/**\n * Output Formatter Utilities for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Pure functions with no side effects\n * - Single responsibility per function\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - External dependencies (keep utilities pure)\n */\n\nimport type { CallToolResult, ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Output format options\n */\nexport type OutputFormat = 'json' | 'text' | 'quiet';\n\n/**\n * Formatter options\n */\nexport interface FormatterOptions {\n format: OutputFormat;\n color: boolean;\n}\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n magenta: '\\x1b[35m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n bold: '\\x1b[1m',\n} as const;\n\n/**\n * Apply color to text if color is enabled\n */\nexport function colorize(text: string, color: keyof typeof COLORS, useColor: boolean): string {\n if (!useColor) {\n return text;\n }\n return `${COLORS[color]}${text}${COLORS.reset}`;\n}\n\n/**\n * Format a CallToolResult for CLI output\n */\nexport function formatToolResult(result: CallToolResult, options: FormatterOptions): string {\n const { format, color } = options;\n\n if (format === 'quiet') {\n return '';\n }\n\n if (format === 'json') {\n return formatAsJson(result);\n }\n\n return formatAsText(result, color);\n}\n\n/**\n * Format result as JSON\n */\nexport function formatAsJson(result: CallToolResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n/**\n * Format result as human-readable text\n */\nexport function formatAsText(result: CallToolResult, useColor: boolean): string {\n const lines: string[] = [];\n\n if (result.isError) {\n lines.push(colorize('Error:', 'red', useColor));\n }\n\n for (const content of result.content) {\n if (content.type === 'text') {\n lines.push(formatTextContent(content as TextContent, useColor));\n } else if (content.type === 'image') {\n lines.push(formatImageContent(content as ImageContent, useColor));\n } else {\n lines.push(colorize(`[Unknown content type: ${content.type}]`, 'yellow', useColor));\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format text content\n */\nexport function formatTextContent(content: TextContent, useColor: boolean): string {\n const text = content.text;\n\n // Try to parse as JSON for pretty printing\n try {\n const parsed = JSON.parse(text);\n return formatParsedJson(parsed, useColor);\n } catch {\n // Not JSON, return as-is\n return text;\n }\n}\n\n/**\n * Format parsed JSON object with optional coloring\n */\nexport function formatParsedJson(data: unknown, useColor: boolean): string {\n if (typeof data !== 'object' || data === null) {\n return String(data);\n }\n\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(data)) {\n const coloredKey = colorize(key, 'cyan', useColor);\n const formattedValue = formatValue(value, useColor);\n lines.push(`${coloredKey}: ${formattedValue}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format a value with type-appropriate coloring\n */\nexport function formatValue(value: unknown, useColor: boolean): string {\n if (value === null) {\n return colorize('null', 'gray', useColor);\n }\n\n if (value === undefined) {\n return colorize('undefined', 'gray', useColor);\n }\n\n if (typeof value === 'boolean') {\n return colorize(String(value), value ? 'green' : 'red', useColor);\n }\n\n if (typeof value === 'number') {\n return colorize(String(value), 'yellow', useColor);\n }\n\n if (typeof value === 'string') {\n // Check if it's a URL\n if (value.startsWith('http://') || value.startsWith('https://')) {\n return colorize(value, 'blue', useColor);\n }\n return value;\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return colorize('[]', 'gray', useColor);\n }\n return JSON.stringify(value, null, 2);\n }\n\n if (typeof value === 'object') {\n return JSON.stringify(value, null, 2);\n }\n\n return String(value);\n}\n\n/**\n * Format image content\n */\nexport function formatImageContent(content: ImageContent, useColor: boolean): string {\n const { mimeType, data } = content;\n const sizeKb = Math.round((data.length * 3) / 4 / 1024);\n\n return colorize(`[Image: ${mimeType}, ~${sizeKb}KB base64 data]`, 'magenta', useColor);\n}\n\n/**\n * Format an error for CLI output\n */\nexport function formatError(error: Error | string, useColor: boolean): string {\n const message = error instanceof Error ? error.message : error;\n return colorize(`Error: ${message}`, 'red', useColor);\n}\n\n/**\n * Format a success message for CLI output\n */\nexport function formatSuccess(message: string, useColor: boolean): string {\n return colorize(`✓ ${message}`, 'green', useColor);\n}\n\n/**\n * Format a warning message for CLI output\n */\nexport function formatWarning(message: string, useColor: boolean): string {\n return colorize(`⚠ ${message}`, 'yellow', useColor);\n}\n\n/**\n * Format a list of tools for help output\n */\nexport function formatToolList(tools: Array<{ name: string; description: string }>, useColor: boolean): string {\n const maxNameLength = Math.max(...tools.map((t) => t.name.length));\n const lines: string[] = [];\n\n for (const tool of tools) {\n const paddedName = tool.name.padEnd(maxNameLength);\n const coloredName = colorize(paddedName, 'cyan', useColor);\n lines.push(` ${coloredName} ${tool.description}`);\n }\n\n return lines.join('\\n');\n}\n","/**\n * Exec Command\n *\n * Execute a tool directly with JSON arguments. Power user command for raw API access.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() and .argument() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from '../utils/outputFormatter.js';\n\ninterface ExecOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\n/**\n * Execute a tool directly with JSON arguments\n */\nexport const execCommand = new Command('exec')\n .description('Execute a tool directly with JSON arguments')\n .argument('<tool>', 'Tool name to execute (e.g., browser_launch)')\n .argument('[args]', 'JSON arguments for the tool', '{}')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n .action(async function (this: Command, tool: string, argsJson: string, options: ExecOptions) {\n const commandDefaults = getCommandConfig<{\n format?: OutputFormat;\n color?: boolean;\n port?: number;\n }>('exec');\n const format = resolveConfiguredOption(this, 'format', options.format as OutputFormat, commandDefaults.format);\n const color = resolveConfiguredOption(this, 'color', options.color, commandDefaults.color);\n const port = resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const formatterOptions: FormatterOptions = {\n format,\n color,\n };\n\n try {\n // Parse JSON arguments\n let args: Record<string, unknown>;\n try {\n args = JSON.parse(argsJson);\n } catch {\n console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n process.exit(1);\n }\n\n // Create client and execute\n const client = createToolClient({\n port: Number.parseInt(port, 10),\n });\n\n const result = await client.execute(tool, args);\n\n // Format and output result\n const output = formatToolResult(result, formatterOptions);\n if (output) {\n console.log(output);\n }\n\n // Exit with error code if tool failed\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\n\n/**\n * Browser data for API response\n */\ninterface BrowserResponse {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: string;\n pages: PageResponse[];\n}\n\n/**\n * Page data for API response\n */\ninterface PageResponse {\n id: string;\n url: string;\n title: string;\n createdAt: string;\n}\n\n/**\n * Stats for API response\n */\ninterface StatsResponse {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Create API router for dashboard\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with API endpoints\n */\nexport function createApiRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET /api/browsers - List all browsers with nested pages and stats\n */\n app.get('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser response with nested pages\n const browsers: BrowserResponse[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt.toISOString(),\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt.toISOString(),\n })),\n };\n });\n\n const stats: StatsResponse = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n return c.json({ browsers, stats });\n } catch (error) {\n console.error('Failed to list browsers:', error);\n return c.json(\n {\n error: 'Failed to list browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers - Close all browsers\n */\n app.delete('/browsers', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n\n return c.json({ success: true, message: 'All browsers closed' });\n } catch (error) {\n console.error('Failed to close all browsers:', error);\n return c.json(\n {\n error: 'Failed to close all browsers',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/browsers/:id - Close a specific browser\n */\n app.delete('/browsers/:id', async (c) => {\n try {\n const browserId = c.req.param('id');\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n const browser = browserService.getBrowser(browserId);\n if (!browser) {\n return c.json({ error: `Browser \"${browserId}\" not found` }, 404);\n }\n\n await browserService.closeBrowser(browserId);\n\n return c.json({ success: true, message: `Browser \"${browserId}\" closed` });\n } catch (error) {\n console.error('Failed to close browser:', error);\n return c.json(\n {\n error: 'Failed to close browser',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n /**\n * DELETE /api/pages/:id - Close a specific page\n */\n app.delete('/pages/:id', async (c) => {\n try {\n const pageId = c.req.param('id');\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const pageEntry = pageRegistry.get(pageId);\n if (!pageEntry) {\n return c.json({ error: `Page \"${pageId}\" not found` }, 404);\n }\n\n if (!pageEntry.page) {\n return c.json({ error: `Page \"${pageId}\" is in extension mode and cannot be closed via API` }, 400);\n }\n\n // Close the page - this will trigger the registry cleanup via the 'close' event\n await pageEntry.page.close();\n\n return c.json({ success: true, message: `Page \"${pageId}\" closed` });\n } catch (error) {\n console.error('Failed to close page:', error);\n return c.json(\n {\n error: 'Failed to close page',\n message: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * Utility formatters for dashboard display\n */\n\n/**\n * Format a Date object to a human-readable string\n *\n * @param date - Date to format\n * @returns Formatted timestamp string\n */\nexport function formatTimestamp(date: Date): string {\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n}\n\n/**\n * Format duration in milliseconds to human readable\n *\n * @param ms - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Calculate duration since a given date\n *\n * @param date - Start date\n * @returns Duration string\n */\nexport function formatAge(date: Date): string {\n const now = new Date();\n const ms = now.getTime() - date.getTime();\n return formatDuration(ms);\n}\n","// Plain CSS class names for SSR compatibility\nexport const container = 'dashboard-container';\nexport const header = 'dashboard-header';\nexport const controlsSection = 'controls-section';\nexport const button = 'btn';\nexport const buttonDanger = 'btn-danger';\nexport const tableContainer = 'table-container';\nexport const table = 'browser-table';\nexport const statsContainer = 'stats-container';\nexport const statCard = 'stat-card';\nexport const browserRow = 'browser-row';\nexport const pageRow = 'page-row';\nexport const statusActive = 'status-active';\nexport const statusInactive = 'status-inactive';\nexport const urlCell = 'url-cell';\nexport const timestampCell = 'timestamp-cell';\nexport const actionsCell = 'actions-cell';\nexport const killButton = 'kill-btn';\nexport const refreshButton = 'refresh-btn';\nexport const emptyState = 'empty-state';\n\n// Global styles as plain CSS string for SSR\nexport const globalStyles = `\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #333;\n background-color: #f5f5f5;\n }\n\n h1, h2, h3 {\n margin-bottom: 1rem;\n }\n\n .dashboard-container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .dashboard-header {\n background-color: #fff;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .dashboard-header h1 {\n margin-bottom: 0;\n }\n\n .controls-section {\n background-color: #fff;\n padding: 1rem;\n margin-bottom: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n display: flex;\n gap: 1rem;\n align-items: center;\n flex-wrap: wrap;\n }\n\n .btn {\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n font-size: 14px;\n cursor: pointer;\n background-color: #2196f3;\n color: white;\n transition: background-color 0.2s;\n }\n\n .btn:hover {\n background-color: #1976d2;\n }\n\n .btn:disabled {\n background-color: #ccc;\n cursor: not-allowed;\n }\n\n .btn-danger {\n background-color: #f44336;\n }\n\n .btn-danger:hover {\n background-color: #d32f2f;\n }\n\n .table-container {\n background-color: #fff;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n\n .browser-table {\n width: 100%;\n border-collapse: collapse;\n table-layout: fixed;\n }\n\n .browser-table th {\n background-color: #f8f9fa;\n padding: 0.75rem;\n text-align: left;\n font-weight: 600;\n border-bottom: 2px solid #dee2e6;\n position: sticky;\n top: 0;\n }\n\n .browser-table th:nth-child(1) {\n width: 12%;\n }\n\n .browser-table th:nth-child(2) {\n width: 10%;\n }\n\n .browser-table th:nth-child(3) {\n width: 38%;\n }\n\n .browser-table th:nth-child(4) {\n width: 15%;\n }\n\n .browser-table th:nth-child(5) {\n width: 15%;\n }\n\n .browser-table th:nth-child(6) {\n width: 10%;\n }\n\n .browser-table td {\n padding: 0.75rem;\n border-bottom: 1px solid #dee2e6;\n vertical-align: middle;\n }\n\n .browser-row {\n background-color: #e3f2fd;\n font-weight: 500;\n }\n\n .browser-row:hover {\n background-color: #bbdefb;\n }\n\n .page-row {\n background-color: #fff;\n }\n\n .page-row:hover {\n background-color: #f8f9fa;\n }\n\n .page-row td:first-child {\n padding-left: 2rem;\n }\n\n .status-active {\n color: #4caf50;\n font-weight: 600;\n }\n\n .status-inactive {\n color: #9e9e9e;\n }\n\n .url-cell {\n max-width: 400px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: 'Courier New', monospace;\n font-size: 13px;\n }\n\n .timestamp-cell {\n white-space: nowrap;\n font-size: 12px;\n color: #666;\n }\n\n .actions-cell {\n text-align: center;\n }\n\n .kill-btn {\n padding: 0.25rem 0.5rem;\n font-size: 12px;\n background-color: #ff5722;\n border: none;\n border-radius: 4px;\n color: white;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .kill-btn:hover {\n background-color: #e64a19;\n }\n\n .refresh-btn {\n background-color: #4caf50;\n }\n\n .refresh-btn:hover {\n background-color: #388e3c;\n }\n\n .stats-container {\n display: flex;\n gap: 1.5rem;\n margin-bottom: 1rem;\n }\n\n .stat-card {\n background-color: #fff;\n padding: 1rem;\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n flex: 1;\n }\n\n .stat-card h3 {\n font-size: 0.875rem;\n color: #666;\n margin-bottom: 0.5rem;\n }\n\n .stat-card .value {\n font-size: 1.5rem;\n font-weight: 700;\n color: #333;\n }\n\n .empty-state {\n padding: 3rem;\n text-align: center;\n color: #666;\n }\n\n .empty-state h3 {\n margin-bottom: 0.5rem;\n color: #999;\n }\n`;\n","import { formatAge, formatTimestamp } from '../utils/formatters.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * BrowserTable component props\n */\ninterface BrowserTableProps {\n browsers: BrowserData[];\n}\n\n/**\n * BrowserTable component\n *\n * Displays browsers and their pages in a hierarchical table\n */\nexport function BrowserTable({ browsers }: BrowserTableProps) {\n if (browsers.length === 0) {\n return (\n <div class={styles.tableContainer}>\n <div class={styles.emptyState}>\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n </div>\n );\n }\n\n return (\n <div class={styles.tableContainer}>\n <table class={styles.table}>\n <thead>\n <tr>\n <th>ID</th>\n <th>Status</th>\n <th>URL / Title</th>\n <th>Created</th>\n <th>Age</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody id=\"browser-table-body\">\n {browsers.map((browser) => (\n <>\n <tr class={styles.browserRow}>\n <td>{browser.id}</td>\n <td>\n <span class={styles.statusActive}>\n {browser.pages.length} page{browser.pages.length !== 1 ? 's' : ''}\n </span>\n </td>\n <td>{browser.profileName || 'Default Profile'}</td>\n <td class={styles.timestampCell}>{formatTimestamp(browser.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(browser.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killBrowser('${browser.id}')`}>\n Kill\n </button>\n </td>\n </tr>\n {browser.pages.map((page) => (\n <tr class={styles.pageRow}>\n <td>\n {page.id}\n {browser.currentPageId === page.id && ' (active)'}\n </td>\n <td>\n <span class={styles.statusActive}>Open</span>\n </td>\n <td class={styles.urlCell} title={page.url}>\n {page.title || page.url || 'about:blank'}\n </td>\n <td class={styles.timestampCell}>{formatTimestamp(page.createdAt)}</td>\n <td class={styles.timestampCell}>{formatAge(page.createdAt)}</td>\n <td class={styles.actionsCell}>\n <button class={styles.killButton} onclick={`dashboard.killPage('${page.id}')`}>\n Close\n </button>\n </td>\n </tr>\n ))}\n </>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n","import * as styles from './styles.js';\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * StatsHeader component props\n */\ninterface StatsHeaderProps {\n stats: Stats;\n}\n\n/**\n * StatsHeader component\n *\n * Displays browser and page statistics\n */\nexport function StatsHeader({ stats }: StatsHeaderProps) {\n return (\n <div class={styles.statsContainer}>\n <div class={styles.statCard}>\n <h3>Active Browsers</h3>\n <div class=\"value\">{stats.totalBrowsers}</div>\n </div>\n <div class={styles.statCard}>\n <h3>Active Pages</h3>\n <div class=\"value\">{stats.totalPages}</div>\n </div>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { BrowserTable } from './BrowserTable.js';\nimport { StatsHeader } from './StatsHeader.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Stats data structure\n */\ninterface Stats {\n totalBrowsers: number;\n totalPages: number;\n}\n\n/**\n * Dashboard component props\n */\ninterface DashboardProps {\n browsers: BrowserData[];\n stats: Stats;\n}\n\n/**\n * Dashboard component\n *\n * Main dashboard UI with auto-refresh polling and browser/page management\n */\nexport function Dashboard({ browsers, stats }: DashboardProps) {\n return (\n <div class={styles.container}>\n <div class={styles.header}>\n <div>\n <h1>Playwright MCP Dashboard</h1>\n <p>Browser automation management (auto-refresh every 3 seconds)</p>\n </div>\n <div>\n <button\n id=\"refresh-btn\"\n class={`${styles.button} ${styles.refreshButton}`}\n onclick=\"dashboard.manualRefresh()\"\n >\n Refresh Now\n </button>\n <button\n id=\"kill-all-btn\"\n class={`${styles.button} ${styles.buttonDanger}`}\n onclick=\"dashboard.killAllBrowsers()\"\n >\n Kill All Browsers\n </button>\n </div>\n </div>\n\n <StatsHeader stats={stats} />\n\n <BrowserTable browsers={browsers} />\n\n <script>\n {raw(`\nclass DashboardManager {\n constructor() {\n this.autoRefreshInterval = null;\n this.isRefreshing = false;\n this.startAutoRefresh();\n }\n\n startAutoRefresh() {\n this.autoRefreshInterval = setInterval(() => {\n this.fetchBrowsers();\n }, 3000);\n }\n\n stopAutoRefresh() {\n if (this.autoRefreshInterval) {\n clearInterval(this.autoRefreshInterval);\n this.autoRefreshInterval = null;\n }\n }\n\n async fetchBrowsers() {\n if (this.isRefreshing) return;\n this.isRefreshing = true;\n\n try {\n const response = await fetch('/api/browsers');\n const data = await response.json();\n this.updateStats(data.stats);\n this.updateBrowserTable(data.browsers);\n } catch (error) {\n console.error('Failed to fetch browsers:', error);\n } finally {\n this.isRefreshing = false;\n }\n }\n\n updateStats(stats) {\n const cards = document.querySelectorAll('.${styles.statCard} .value');\n if (cards.length >= 2) {\n cards[0].textContent = stats.totalBrowsers;\n cards[1].textContent = stats.totalPages;\n }\n }\n\n updateBrowserTable(browsers) {\n const tbody = document.getElementById('browser-table-body');\n if (!tbody) return;\n\n if (browsers.length === 0) {\n const container = tbody.closest('.${styles.tableContainer}');\n if (container) {\n container.innerHTML = \\`\n <div class=\"${styles.emptyState}\">\n <h3>No Active Browsers</h3>\n <p>Launch a browser using MCP tools to see it here.</p>\n </div>\n \\`;\n }\n return;\n }\n\n // Rebuild table content\n tbody.innerHTML = '';\n browsers.forEach(browser => {\n // Browser row\n const browserRow = document.createElement('tr');\n browserRow.className = '${styles.browserRow}';\n browserRow.innerHTML = \\`\n <td>\\${browser.id}</td>\n <td><span class=\"${styles.statusActive}\">\\${browser.pages.length} page\\${browser.pages.length !== 1 ? 's' : ''}</span></td>\n <td>\\${browser.profileName || 'Default Profile'}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(browser.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(browser.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killBrowser('\\${browser.id}')\">Kill</button></td>\n \\`;\n tbody.appendChild(browserRow);\n\n // Page rows\n browser.pages.forEach(page => {\n const pageRow = document.createElement('tr');\n pageRow.className = '${styles.pageRow}';\n const isActive = browser.currentPageId === page.id;\n pageRow.innerHTML = \\`\n <td>\\${page.id}\\${isActive ? ' (active)' : ''}</td>\n <td><span class=\"${styles.statusActive}\">Open</span></td>\n <td class=\"${styles.urlCell}\" title=\"\\${this.escapeHtml(page.url)}\">\\${this.escapeHtml(page.title || page.url || 'about:blank')}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatTimestamp(page.createdAt)}</td>\n <td class=\"${styles.timestampCell}\">\\${this.formatAge(page.createdAt)}</td>\n <td class=\"${styles.actionsCell}\"><button class=\"${styles.killButton}\" onclick=\"dashboard.killPage('\\${page.id}')\">Close</button></td>\n \\`;\n tbody.appendChild(pageRow);\n });\n });\n }\n\n formatTimestamp(timestamp) {\n const date = new Date(timestamp);\n return date.toLocaleString('en-US', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n }\n\n formatAge(timestamp) {\n const now = new Date();\n const date = new Date(timestamp);\n const ms = now.getTime() - date.getTime();\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) return \\`\\${hours}h \\${minutes % 60}m\\`;\n if (minutes > 0) return \\`\\${minutes}m \\${seconds % 60}s\\`;\n return \\`\\${seconds}s\\`;\n }\n\n escapeHtml(str) {\n if (typeof str !== 'string') return str;\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n }\n\n manualRefresh() {\n this.fetchBrowsers();\n }\n\n async killBrowser(browserId) {\n try {\n const response = await fetch(\\`/api/browsers/\\${browserId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill browser:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill browser:', error);\n }\n }\n\n async killPage(pageId) {\n try {\n const response = await fetch(\\`/api/pages/\\${pageId}\\`, { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to close page:', await response.text());\n }\n } catch (error) {\n console.error('Failed to close page:', error);\n }\n }\n\n async killAllBrowsers() {\n if (!confirm('Are you sure you want to close all browsers?')) return;\n\n try {\n const response = await fetch('/api/browsers', { method: 'DELETE' });\n if (response.ok) {\n this.fetchBrowsers();\n } else {\n console.error('Failed to kill all browsers:', await response.text());\n }\n } catch (error) {\n console.error('Failed to kill all browsers:', error);\n }\n }\n}\n\n// Initialize dashboard\nconst dashboard = new DashboardManager();\n\n// Cleanup on page unload\nwindow.addEventListener('beforeunload', () => {\n dashboard.stopAutoRefresh();\n});\n `)}\n </script>\n </div>\n );\n}\n","import { raw } from 'hono/html';\nimport { globalStyles } from './styles.js';\n\n/**\n * Layout component props\n */\ninterface LayoutProps {\n title: string;\n children: any;\n}\n\n/**\n * Layout component\n *\n * Provides the HTML wrapper with styles and meta tags for SSR\n */\nexport function Layout({ title, children }: LayoutProps) {\n return (\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>{title}</title>\n <style>{raw(globalStyles)}</style>\n </head>\n <body>{children}</body>\n </html>\n );\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\nimport { Dashboard } from '../components/Dashboard.js';\nimport { Layout } from '../components/Layout.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n id: string;\n url: string;\n title: string;\n createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n id: string;\n profileName?: string;\n currentPageId: string | null;\n createdAt: Date;\n pages: PageData[];\n}\n\n/**\n * Create dashboard router for server-side rendered UI\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with dashboard routes\n */\nexport function createDashboardRouter(container: Container): Hono {\n const app = new Hono();\n\n /**\n * GET / - Dashboard home page with SSR\n *\n * Renders the dashboard UI with initial server-side data\n */\n app.get('/', async (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n const browserInstances = browserService.listBrowsers();\n const allPages = pageRegistry.list();\n\n // Build browser data with nested pages\n const browsers: BrowserData[] = browserInstances.map((browser: BrowserInstance) => {\n const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n return {\n id: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n createdAt: browser.createdAt,\n pages: browserPages.map((page: PageEntry) => ({\n id: page.id,\n url: page.url,\n title: page.title,\n createdAt: page.createdAt,\n })),\n };\n });\n\n const stats = {\n totalBrowsers: browserInstances.length,\n totalPages: allPages.length,\n };\n\n // Render dashboard with Layout wrapper\n return c.html(\n <Layout title=\"Playwright MCP Dashboard\">\n <Dashboard browsers={browsers} stats={stats} />\n </Layout>,\n );\n } catch (error) {\n console.error('Failed to render dashboard:', error);\n\n return c.html(\n <Layout title=\"Playwright MCP Dashboard - Error\">\n <div style=\"padding: 2rem; text-align: center;\">\n <h1>Failed to load dashboard</h1>\n <p>{error instanceof Error ? error.message : String(error)}</p>\n <a href=\"/\" style=\"color: #2196f3; text-decoration: underline;\">\n Retry\n </a>\n </div>\n </Layout>,\n 500,\n );\n }\n });\n\n return app;\n}\n","/**\n * HTTP Server for Browser Automation\n *\n * DESIGN PATTERNS:\n * - Hono for HTTP routing and middleware\n * - Dependency injection with InversifyJS Container\n * - Service layer for business logic\n * - Generic execute endpoint for all tools\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing and middleware\n * - Use CORS middleware for cross-origin requests\n * - Keep route handlers thin, delegate to services\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../services/BrowserService.js';\nimport type { Tool } from '../types/index.js';\nimport { createApiRouter } from './dashboard/routes/api.js';\nimport { createDashboardRouter } from './dashboard/routes/dashboard.js';\nimport { createExtensionRoutes } from './extension-routes.js';\n\n/**\n * Execute request body\n */\ninterface ExecuteRequest {\n tool: string;\n arguments: Record<string, unknown>;\n}\n\n/**\n * Execute response body\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\n/**\n * Browser info for health response\n */\ninterface BrowserInfo {\n id: string;\n pageCount: number;\n createdAt: string;\n}\n\n/**\n * Health response\n */\ninterface HealthResponse {\n status: 'healthy' | 'unhealthy';\n service: string;\n serviceName: string;\n timestamp: string;\n browsers: {\n count: number;\n instances: BrowserInfo[];\n };\n}\n\n/**\n * Create HTTP server for browser automation\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono app\n */\nexport function createHttpServer(container: Container): Hono {\n const app = new Hono();\n\n // CORS middleware - allow all origins\n app.use(\n '*',\n cors({\n origin: (origin) => origin ?? null,\n credentials: true,\n allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Accept', 'Authorization'],\n }),\n );\n\n // Health check endpoint\n app.get('/health', (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const browsers = browserService.listBrowsers();\n\n const response: HealthResponse = {\n status: 'healthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n timestamp: new Date().toISOString(),\n browsers: {\n count: browsers.length,\n instances: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n pageCount: b.pageIds.size,\n createdAt: b.createdAt.toISOString(),\n })),\n },\n };\n\n return c.json(response);\n } catch (error) {\n return c.json(\n {\n status: 'unhealthy',\n service: 'browse-tool-http',\n serviceName: 'browse-tool-http',\n error: error instanceof Error ? error.message : String(error),\n },\n 503,\n );\n }\n });\n\n // Execute tool endpoint\n app.post('/execute', async (c) => {\n try {\n const body = (await c.req.json()) as ExecuteRequest;\n\n if (!body.tool) {\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: 'Missing \"tool\" field in request body',\n },\n 400,\n );\n }\n\n // Get all tools from container\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n // Find matching tool\n const tool = tools.find((t) => t.getDefinition().name === body.tool);\n\n if (!tool) {\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: `Tool \"${body.tool}\" not found`,\n },\n 404,\n );\n }\n\n // Execute tool\n const result = await tool.execute(body.arguments || {});\n\n // Track activity for idle cleanup\n const args = body.arguments || {};\n const pageId = args.pageId as string | undefined;\n if (pageId) {\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n pageRegistry.touchPage(pageId);\n const pageEntry = pageRegistry.get(pageId);\n if (pageEntry) {\n browserService.touchBrowser(pageEntry.browserId);\n }\n }\n\n // Extract error message from result content if present\n const errorMessage = result.isError ? (result.content[0] as { text?: string })?.text : undefined;\n\n return c.json<ExecuteResponse>({\n success: !result.isError,\n result,\n error: errorMessage,\n });\n } catch (error) {\n return c.json<ExecuteResponse>(\n {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List browsers endpoint\n app.get('/browsers', (c) => {\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const browsers = browserService.listBrowsers();\n\n return c.json({\n browsers: browsers.map((b: BrowserInstance) => ({\n id: b.id,\n profileName: b.profileName,\n pageIds: Array.from(b.pageIds),\n currentPageId: b.currentPageId,\n createdAt: b.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // List tools endpoint\n app.get('/tools', (c) => {\n try {\n const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n return c.json({\n tools: tools.map((t) => t.getDefinition()),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n\n // Mount extension routes for Chrome extension communication\n const extensionRouter = createExtensionRoutes(container);\n app.route('/extension', extensionRouter);\n\n // Mount dashboard API routes\n const apiRouter = createApiRouter(container);\n app.route('/api', apiRouter);\n\n // Mount dashboard UI routes (SSR)\n const dashboardRouter = createDashboardRouter(container);\n app.route('/', dashboardRouter);\n\n return app;\n}\n","/**\n * WebSocket Routes for Extension Communication\n *\n * DESIGN PATTERNS:\n * - WebSocket upgrade handler using @hono/node-ws\n * - Dependency injection with InversifyJS Container\n * - Pub/sub pattern via WebSocketHub\n *\n * CODING STANDARDS:\n * - Use Hono WebSocket helpers for upgrade\n * - Keep route handlers thin, delegate to WebSocketHub\n * - Handle connection lifecycle properly\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n * - Memory leaks from uncleaned connections\n */\n\nimport type { Hono } from 'hono';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\n\n/**\n * Setup WebSocket routes for extension communication\n *\n * @param app - Hono app instance\n * @param container - InversifyJS container with services\n * @param upgradeWebSocket - WebSocket upgrade function from @hono/node-ws\n */\nexport function setupWebSocketRoutes(app: Hono, container: Container, upgradeWebSocket: UpgradeWebSocket): void {\n /**\n * WebSocket endpoint for extension connections\n * Path: /ws/extension/:browserId\n */\n app.get(\n '/ws/extension/:browserId',\n upgradeWebSocket((c) => {\n const browserId = c.req.param('browserId');\n let connectionId: string | null = null;\n\n return {\n onOpen(_event, ws) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n connectionId = wsHub.addConnection(ws, browserId);\n } catch {\n ws.close(1011, 'Internal server error');\n }\n },\n\n onMessage(event) {\n if (!connectionId) {\n return;\n }\n\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const data = typeof event.data === 'string' ? event.data : event.data.toString();\n wsHub.handleMessage(connectionId, data);\n } catch {\n // Error handling delegated to WebSocketHub\n }\n },\n\n onClose() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n\n onError() {\n if (connectionId) {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n wsHub.removeConnection(connectionId);\n } catch {\n // Ignore cleanup errors\n }\n }\n },\n };\n }),\n );\n\n /**\n * GET /ws/stats\n * Get WebSocket connection statistics\n */\n app.get('/ws/stats', (c) => {\n try {\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const stats = wsHub.getStats();\n\n return c.json({\n totalConnections: stats.totalConnections,\n browserCount: stats.browserCount,\n connections: stats.connections.map((conn) => ({\n id: conn.id,\n browserId: conn.browserId,\n connectedAt: conn.connectedAt.toISOString(),\n })),\n });\n } catch (error) {\n return c.json(\n {\n error: error instanceof Error ? error.message : String(error),\n },\n 500,\n );\n }\n });\n}\n","/**\n * HTTP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n * - Dependency injection with InversifyJS Container\n * - Graceful shutdown pattern for SIGINT/SIGTERM\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n * - Implement graceful shutdown for server cleanup\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n * - Missing signal handlers for graceful shutdown\n */\n\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createHttpContainer } from '../container/index.js';\nimport { createHttpServer } from '../server/http.js';\nimport { setupWebSocketRoutes } from '../server/websocket-routes.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport type { IIdleCleanupService } from '../services/IdleCleanupService.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\nimport { DEFAULT_MCP_PORT, buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\ninterface HttpServeOptions {\n port: string;\n headless: boolean;\n idleTimeout: string;\n host: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n}\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\n\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let current = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(path.join(current, marker))) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n return process.cwd();\n }\n current = parent;\n }\n}\n\n/**\n * Start HTTP server for browser automation\n */\nexport const httpServeCommand = new Command('http-serve')\n .description('Start HTTP server for browser automation')\n .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_MCP_PORT))\n .option('--host <host>', 'Host to bind', getPlaywrightHost())\n .option('--headless', 'Run browsers in headless mode by default', true)\n .option('--idle-timeout <minutes>', 'Idle timeout in minutes before closing browsers', '30')\n .option('--registry-dir <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking (deprecated)')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .action(async function (this: Command, options: HttpServeOptions) {\n const commandDefaults = getCommandConfig<{\n port?: number;\n headless?: boolean;\n idleTimeout?: number;\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n profilesDir?: string;\n }>('httpServe');\n const resolvedOptions: HttpServeOptions = {\n port: resolveConfiguredOption(\n this,\n 'port',\n options.port,\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n ),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n idleTimeout: resolveConfiguredOption(\n this,\n 'idleTimeout',\n options.idleTimeout,\n commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n ),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n };\n try {\n const requestedPort = Number.parseInt(resolvedOptions.port, 10);\n const serviceName = 'browse-tool-http';\n const environment = process.env.NODE_ENV || 'development';\n const repositoryPath = resolveWorkspaceRoot(process.cwd());\n\n // Set environment variables from CLI options\n const registryPath =\n resolvedOptions.registryPath || resolvedOptions.registryDir || process.env.PLAYWRIGHT_REGISTRY_PATH;\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n process.env.PLAYWRIGHT_HOST = resolvedOptions.host;\n process.env.PLAYWRIGHT_PORT = resolvedOptions.port;\n\n if (resolvedOptions.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = resolvedOptions.pidsDir;\n }\n if (resolvedOptions.profilesDir) {\n process.env.PLAYWRIGHT_PROFILES_DIR = resolvedOptions.profilesDir;\n }\n\n console.log('Starting HTTP server for browser automation...');\n console.log(` Port: ${requestedPort}`);\n console.log(` Host: ${resolvedOptions.host}`);\n console.log(` Headless: ${resolvedOptions.headless}`);\n console.log(` Idle Timeout: ${resolvedOptions.idleTimeout} minutes`);\n\n // Create InversifyJS container with full HTTP server services\n const container = createHttpContainer();\n\n // Create HTTP server\n const app = createHttpServer(container);\n\n // Setup WebSocket support for extension communication\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n setupWebSocketRoutes(app, container, upgradeWebSocket);\n\n // Configure WebSocketHub event handlers for extension communication\n const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n PLAYWRIGHT_TYPES.PageRegistry,\n );\n\n // Start idle cleanup service\n const idleCleanupService = container.get<IIdleCleanupService>(PLAYWRIGHT_TYPES.IdleCleanupService);\n idleCleanupService.start();\n\n wsHub.setEventHandlers({\n onSessionRegister: (connection, message) => {\n if (message.type === 'session:register') {\n // Find an existing extension-mode browser that doesn't have a WebSocket connection yet\n // This handles the case where browser_launch created a browser and we're now connecting\n const existingBrowser = browserService\n .listBrowsers()\n .find((b) => (b.mode === 'extension' || b.mode === 'vm') && !wsHub.hasConnection(b.id));\n\n let browserId: string;\n let pageId: string;\n\n if (existingBrowser) {\n // Use the existing browser that was created by browser_launch\n browserId = existingBrowser.id;\n pageId = existingBrowser.currentPageId || Array.from(existingBrowser.pageIds)[0] || '';\n\n // Reassign the WebSocket connection to use the existing browser's ID\n wsHub.reassignConnection(connection.id, browserId);\n\n console.log(`[WebSocket] Extension connected to existing browser: ${browserId}, pageId: ${pageId}`);\n } else {\n // No existing browser - create a new one (extension connected independently)\n browserId = connection.browserId;\n browserService.registerExtensionBrowserWithId(browserId);\n pageId = pageRegistry.registerExtensionPage(browserId);\n\n const browserInstance = browserService.getBrowser(browserId);\n if (browserInstance) {\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n }\n\n console.log(`[WebSocket] Extension connected with new browser: ${browserId}, pageId: ${pageId}`);\n }\n\n const sessionId = `session-${Date.now()}`;\n wsHub.sendSessionAck(connection, message.id ?? '', sessionId, 'extension');\n\n // Notify extension about the page so it can map to its Chrome tab\n wsHub.broadcastPageCreated(browserId, pageId);\n }\n },\n onTaskResult: (_connection, message) => {\n if (message.type === 'task:result') {\n taskQueue.submitResult({\n taskId: message.payload.taskId,\n success: message.payload.success,\n result: message.payload.result,\n error: message.payload.error,\n });\n }\n },\n onDisconnect: (connection) => {\n console.log(`[WebSocket] Extension disconnected: ${connection.browserId}`);\n },\n });\n\n // Register service in global registry with the exact requested port.\n // Port fallback/retry logic is handled by HttpServerManager, not here.\n const portRegistry = new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n\n if (registryPath) {\n console.log(` Registry path: ${registryPath}`);\n } else {\n console.log(' Registry path: default (~/.port-registry/ports.json)');\n }\n\n const result = await portRegistry.reservePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n preferredPort: requestedPort,\n pid: process.pid,\n host: resolvedOptions.host,\n force: true,\n portRange: { min: requestedPort, max: requestedPort },\n metadata: {\n healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host, requestedPort)}/health`,\n headless: resolvedOptions.headless,\n idleTimeout: resolvedOptions.idleTimeout,\n },\n });\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to reserve port ${requestedPort} in global registry`);\n }\n\n const finalPort = requestedPort;\n\n // Start server with WebSocket support\n let server;\n try {\n server = serve({\n fetch: app.fetch,\n port: finalPort,\n hostname: resolvedOptions.host,\n });\n } catch (error) {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n throw error;\n }\n\n // Inject WebSocket handler into the server\n injectWebSocket(server);\n\n const baseUrl = buildPlaywrightBaseUrl(resolvedOptions.host, finalPort);\n console.log(`HTTP server listening on ${baseUrl}`);\n console.log(` Health check: ${baseUrl}/health`);\n console.log(` Execute tool: POST ${baseUrl}/execute`);\n\n console.log('\\nPress Ctrl+C to stop the server');\n\n // Graceful shutdown handling\n const shutdown = async (signal: string) => {\n console.log(`\\n\\n${signal} received. Shutting down gracefully...`);\n\n try {\n // Stop idle cleanup\n idleCleanupService.stop();\n\n // Close server\n server.close();\n console.log('Server closed');\n\n // Close all browsers\n try {\n const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n await browserService.closeAll();\n console.log('All browsers closed');\n } catch {\n // Browser service may not have browsers\n }\n\n // Deregister service\n try {\n await portRegistry.releasePort({\n repositoryPath,\n serviceName,\n serviceType: 'tool',\n environment,\n pid: process.pid,\n });\n console.log('Service deregistered');\n } catch {\n // Ignore errors during cleanup\n }\n\n console.log('Goodbye!');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n } catch (error) {\n console.error('Error starting HTTP server:', error);\n process.exit(1);\n }\n });\n","/**\n * Tool tag definitions for filtering MCP tools by category.\n *\n * DESIGN PATTERNS:\n * - Centralized tag registry pattern\n * - Maps tool names to tag arrays for proxy-level filtering\n * - Tags are additive — a tool can belong to multiple categories\n *\n * CODING STANDARDS:\n * - Keep tag names lowercase, single-word where possible\n * - Keep map in sync with tool registrations in container\n * - Add new tools here when creating them\n *\n * AVOID:\n * - Defining tags in individual tool files (single source of truth)\n * - Using tags not listed in AVAILABLE_TAGS\n */\n\n/** All recognized tag values */\nexport const AVAILABLE_TAGS = [\n 'input',\n 'navigation',\n 'snapshot',\n 'page',\n 'dialog',\n 'network',\n 'console',\n 'script',\n 'emulation',\n 'testing',\n 'tracing',\n 'profile',\n 'browser',\n 'spec',\n 'code',\n] as const;\n\nexport type ToolTag = (typeof AVAILABLE_TAGS)[number];\n\n/** Maps each tool name to its tags */\nexport const TOOL_TAGS: Record<string, ToolTag[]> = {\n // Input tools\n browser_click: ['input'],\n browser_fill: ['input'],\n browser_type: ['input'],\n browser_select: ['input'],\n browser_hover: ['input'],\n browser_drag: ['input'],\n browser_press_key: ['input'],\n browser_upload_file: ['input'],\n // Navigation tools\n browser_navigate: ['navigation'],\n browser_go_back: ['navigation'],\n browser_go_forward: ['navigation'],\n browser_reload: ['navigation'],\n browser_wait_for: ['navigation'],\n // Snapshot tools\n browser_snapshot: ['snapshot'],\n browser_screenshot: ['snapshot'],\n browser_pdf: ['snapshot'],\n // Page management tools\n browser_list_pages: ['page'],\n browser_new_page: ['page'],\n browser_select_page: ['page'],\n browser_close_page: ['page'],\n browser_resize_page: ['page', 'emulation'],\n // Dialog tools\n browser_handle_dialog: ['dialog'],\n // Network tools\n browser_list_network_requests: ['network'],\n browser_get_network_request: ['network'],\n // Console tools\n browser_list_console_messages: ['console'],\n // Script tools\n browser_evaluate_script: ['script'],\n // Emulation tools\n browser_emulate: ['emulation'],\n // Testing and tracing tools\n browser_expect: ['testing'],\n browser_start_trace: ['tracing'],\n browser_stop_trace: ['tracing'],\n // Profile management tools\n browser_list_profiles: ['profile'],\n browser_delete_profile: ['profile'],\n // Spec tools\n run_spec: ['spec', 'testing'],\n discover_specs: ['spec', 'testing'],\n // Browser lifecycle tools\n browser_launch: ['browser'],\n browser_close: ['browser'],\n // Code execution tools\n browser_run_code: ['code', 'script'],\n};\n\n/**\n * Resolves a set of tag names to matching tool names.\n * @param tags - Tags to include\n * @returns Set of tool names that match any of the given tags\n */\nexport function getToolNamesByTags(tags: string[]): Set<string> {\n const result = new Set<string>();\n for (const [toolName, toolTags] of Object.entries(TOOL_TAGS)) {\n if (toolTags.some((t) => tags.includes(t))) {\n result.add(toolName);\n }\n }\n return result;\n}\n","/**\n * Proxy MCP Server\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for forwarding requests to HTTP server\n * - MCP Server setup pattern with request handlers\n * - Session tracking for cleanup on shutdown\n *\n * CODING STANDARDS:\n * - Create server instance using Server from @modelcontextprotocol/sdk\n * - Use proper error handling in request handlers\n * - Export factory function for server creation\n * - Track browsers/pages for session cleanup\n *\n * AVOID:\n * - Business logic in handlers\n * - Hardcoded URLs or configuration\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { getToolNamesByTags } from '../constants/tool-tags.js';\nimport type { IMcpSessionTracker } from '../services/McpSessionTracker.js';\nimport type { ToolDefinition } from '../types/index.js';\n\n/** Browser launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/**\n * Tool filtering configuration for proxy server.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed from the result.\n */\nexport interface ToolFilterConfig {\n /** Include only tools matching these tags */\n tags?: string[];\n /** Exclude specific tools by name (applied after tag filtering) */\n exclude?: string[];\n}\n\n/**\n * Configuration for the proxy server\n */\nexport interface ProxyServerConfig {\n /** Base URL of the HTTP server to proxy requests to */\n httpBaseUrl: string;\n /** Session tracker for tracking browsers/pages created in this session */\n sessionTracker?: IMcpSessionTracker;\n /** Default launch mode for browser_launch tool when not specified */\n defaultMode?: LaunchMode;\n /** Tool filtering options */\n toolFilter?: ToolFilterConfig;\n}\n\n/**\n * Response from the /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\n/**\n * Response from the /execute endpoint\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\nconst TOOLS_REQUEST_TIMEOUT_MS = 3000;\nconst TOOLS_REQUEST_MAX_ATTEMPTS = 4;\nconst TOOLS_REQUEST_RETRY_BASE_DELAY_MS = 150;\n\n/**\n * Creates a proxy MCP server that forwards all requests to an HTTP server.\n *\n * This server acts as a thin proxy layer:\n * - ListTools: Fetches tool definitions from HTTP server's /tools endpoint\n * - CallTool: Forwards tool execution to HTTP server's /execute endpoint\n *\n * @param config - Proxy server configuration\n * @returns Configured MCP Server instance\n */\n/** Tools that create browsers */\nconst BROWSER_CREATING_TOOLS = ['browser_launch'];\n/** Tools that create pages */\nconst PAGE_CREATING_TOOLS = ['browser_new_page'];\n\n/**\n * Parse browser/page info from tool result\n */\nfunction parseResourceFromResult(\n result: ExecuteResponse['result'],\n): { browserId?: string; pageId?: string; mode?: string; url?: string } | null {\n if (!result?.content?.[0]?.text) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(result.content[0].text);\n return {\n browserId: parsed.browserId,\n pageId: parsed.pageId,\n mode: parsed.mode,\n url: parsed.url,\n };\n } catch {\n return null;\n }\n}\n\nasync function wait(delayMs: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n\nasync function fetchToolsFromHttpServer(httpBaseUrl: string): Promise<ToolDefinition[]> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(`${httpBaseUrl}/tools`, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n if (data.error) {\n throw new Error(data.error);\n }\n\n if (!Array.isArray(data.tools)) {\n throw new Error('Invalid /tools response: missing tools array');\n }\n\n if (data.tools.length === 0) {\n throw new Error('HTTP server returned an empty tool list');\n }\n\n return data.tools;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nasync function fetchToolsWithRetry(httpBaseUrl: string): Promise<ToolDefinition[]> {\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n try {\n return await fetchToolsFromHttpServer(httpBaseUrl);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n }\n }\n }\n\n throw new Error(\n `Failed to fetch tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n { cause: lastError },\n );\n}\n\n/**\n * Build the set of allowed tool names based on filter config.\n * Returns null if no filtering is configured (all tools allowed).\n */\nfunction buildAllowedToolNames(filter?: ToolFilterConfig): Set<string> | null {\n if (!filter?.tags?.length && !filter?.exclude?.length) {\n return null;\n }\n\n const taggedNames = filter.tags?.length ? getToolNamesByTags(filter.tags) : null;\n const excludeSet = new Set(filter.exclude ?? []);\n\n if (!taggedNames) {\n // Only exclude, no tag filter — return null to allow all, exclusion applied separately\n return excludeSet.size > 0 ? excludeSet : null;\n }\n\n // Remove excluded tools from the tag-matched set\n for (const name of excludeSet) {\n taggedNames.delete(name);\n }\n\n return taggedNames;\n}\n\nexport function createProxyServer(config: ProxyServerConfig): Server {\n const { httpBaseUrl, sessionTracker, defaultMode, toolFilter } = config;\n\n // Pre-compute allowed tool names for filtering\n const allowedToolNames = toolFilter?.tags?.length ? buildAllowedToolNames(toolFilter) : null;\n const excludedToolNames = new Set(toolFilter?.exclude ?? []);\n const hasFilter = allowedToolNames !== null || excludedToolNames.size > 0;\n\n const server = new Server(\n {\n name: 'browse-tool',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n },\n );\n\n // Session-specific tool definition\n const sessionToolDefinition: ToolDefinition = {\n name: 'browser_list_session',\n description:\n 'List all browsers and pages created in this MCP session. Use this to see what resources you have available.',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n\n let cachedTools: ToolDefinition[] = [];\n\n /**\n * Apply tool filtering based on --tags and --exclude options.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed regardless.\n */\n function filterTools(tools: ToolDefinition[]): ToolDefinition[] {\n if (!hasFilter) {\n return tools;\n }\n\n return tools.filter((tool) => {\n if (excludedToolNames.has(tool.name)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(tool.name);\n }\n return true;\n });\n }\n\n /**\n * Check if a tool name is allowed by the current filter.\n */\n function isToolAllowed(toolName: string): boolean {\n if (excludedToolNames.has(toolName)) {\n return false;\n }\n if (allowedToolNames !== null) {\n return allowedToolNames.has(toolName);\n }\n return true;\n }\n\n /**\n * ListTools handler - fetches tool definitions from HTTP server + session tool\n */\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n try {\n const toolsFromServer = await fetchToolsWithRetry(httpBaseUrl);\n cachedTools = toolsFromServer;\n\n // Filter tools based on --tags and --exclude\n const tools = filterTools(toolsFromServer);\n\n // Add session-specific tool if tracker is available\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n\n return { tools };\n } catch (error) {\n if (cachedTools.length > 0) {\n console.error(\n 'Failed to refresh tools from HTTP server. Returning cached tools:',\n error instanceof Error ? error.message : String(error),\n );\n\n const tools = filterTools(cachedTools);\n if (sessionTracker) {\n tools.push(sessionToolDefinition);\n }\n return { tools };\n }\n\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to list tools from HTTP server at ${httpBaseUrl}: ${message}`, {\n cause: error instanceof Error ? error : undefined,\n });\n }\n });\n\n /**\n * CallTool handler - forwards tool execution to HTTP server\n */\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n // Reject filtered-out tools\n if (name !== 'browser_list_session' && !isToolAllowed(name)) {\n return {\n content: [{ type: 'text', text: `Tool \"${name}\" is not available (filtered by --tags or --exclude)` }],\n isError: true,\n };\n }\n\n // Handle session-specific tool locally\n if (name === 'browser_list_session' && sessionTracker) {\n const state = sessionTracker.getSessionState();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(state, null, 2),\n },\n ],\n };\n }\n\n // Prepare arguments, injecting defaults where needed\n let finalArgs = args || {};\n\n // Enforce default mode for browser_launch when configured\n // When default mode is set, always use it (don't allow override)\n if (name === 'browser_launch' && defaultMode) {\n finalArgs = { ...finalArgs, mode: defaultMode };\n }\n\n try {\n const response = await fetch(`${httpBaseUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ tool: name, arguments: finalArgs }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n content: [{ type: 'text', text: `HTTP error ${response.status}: ${errorText}` }],\n isError: true,\n };\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n const errorText = data.error || data.result?.content?.[0]?.text || 'Unknown error from HTTP server';\n return {\n content: [{ type: 'text', text: errorText }],\n isError: true,\n };\n }\n\n // Track browsers/pages created in this session\n if (sessionTracker && data.result) {\n if (BROWSER_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.browserId) {\n sessionTracker.trackBrowser(\n resource.browserId,\n (resource.mode as 'playwright' | 'extension' | 'vm' | 'stealth') || 'playwright',\n );\n if (resource.pageId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n } else if (PAGE_CREATING_TOOLS.includes(name)) {\n const resource = parseResourceFromResult(data.result);\n if (resource?.pageId && resource?.browserId) {\n sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n }\n }\n }\n\n return data.result || { content: [{ type: 'text', text: 'No result returned' }] };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Failed to execute tool via HTTP server: ${errorMessage}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES, createMcpContainer } from '../container/index.js';\nimport type { ToolFilterConfig } from '../server/proxy.js';\nimport { createProxyServer } from '../server/proxy.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { McpSessionTracker } from '../services/McpSessionTracker.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost, getPlaywrightPort } from '../utils/networkConfig.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst TRANSPORT_STDIO = 'stdio';\nconst SUPPORTED_TRANSPORTS = new Set([TRANSPORT_STDIO]);\n\nconst BROWSER_CHROMIUM = 'chromium';\nconst BROWSER_FIREFOX = 'firefox';\nconst BROWSER_WEBKIT = 'webkit';\nconst SUPPORTED_BROWSERS = new Set([BROWSER_CHROMIUM, BROWSER_FIREFOX, BROWSER_WEBKIT]);\n\nconst MODE_PLAYWRIGHT = 'playwright';\nconst MODE_EXTENSION = 'extension';\nconst MODE_VM = 'vm';\nconst MODE_STEALTH = 'stealth';\nconst SUPPORTED_LAUNCH_MODES = new Set([MODE_PLAYWRIGHT, MODE_EXTENSION, MODE_VM, MODE_STEALTH]);\n\nconst BROWSER_CLOSE_TOOL = 'browser_close';\nconst CSV_SEPARATOR = ',';\n\nconst HTTP_STATUS_SPAWNED = 'spawned';\nconst HTTP_STATUS_REUSED = 'reused';\n\nconst SIGNAL_SIGINT = 'SIGINT';\nconst SIGNAL_SIGTERM = 'SIGTERM';\n\nconst ENV_REGISTRY_DIR = 'PLAYWRIGHT_REGISTRY_DIR';\nconst ENV_PORT_REGISTRY_PATH = 'PORT_REGISTRY_PATH';\nconst ENV_PIDS_DIR = 'PLAYWRIGHT_PIDS_DIR';\nconst ENV_PROFILES_DIR = 'PLAYWRIGHT_PROFILES_DIR';\nconst ENV_HOST = 'PLAYWRIGHT_HOST';\nconst ENV_PORT = 'PLAYWRIGHT_PORT';\n\nconst EXIT_CODE_SUCCESS = 0;\nconst EXIT_CODE_FAILURE = 1;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Supported browser types */\nexport type BrowserType = 'chromium' | 'firefox' | 'webkit';\n\n/** Supported launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/** CLI options for mcp-serve command */\nexport interface McpServeOptions {\n type: string;\n browser: BrowserType;\n headless: boolean;\n profile?: string;\n mode?: LaunchMode;\n registryPath?: string;\n host?: string;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n tags?: string;\n exclude?: string;\n}\n\n/** Interface for transport handlers */\ninterface TransportHandler {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport class InvalidTransportError extends Error {\n readonly code = 'INVALID_TRANSPORT';\n readonly recovery = `Use --type ${TRANSPORT_STDIO} (currently the only supported transport)`;\n readonly transportType: string;\n\n constructor(transportType: string, options?: ErrorOptions) {\n super(`Unknown transport type: ${transportType}`, options);\n this.name = 'InvalidTransportError';\n this.transportType = transportType;\n }\n}\n\nexport class InvalidBrowserTypeError extends Error {\n readonly code = 'INVALID_BROWSER_TYPE';\n readonly recovery = `Use --browser ${[...SUPPORTED_BROWSERS].join(', ')}`;\n readonly browserType: string;\n\n constructor(browserType: string, options?: ErrorOptions) {\n super(`Unknown browser type: ${browserType}`, options);\n this.name = 'InvalidBrowserTypeError';\n this.browserType = browserType;\n }\n}\n\nexport class InvalidLaunchModeError extends Error {\n readonly code = 'INVALID_LAUNCH_MODE';\n readonly recovery = `Use --mode ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`;\n readonly launchMode: string;\n\n constructor(launchMode: string, options?: ErrorOptions) {\n super(`Unknown launch mode: ${launchMode}`, options);\n this.name = 'InvalidLaunchModeError';\n this.launchMode = launchMode;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction parseBrowserType(value: string): BrowserType {\n const browserType = value.toLowerCase();\n if (!SUPPORTED_BROWSERS.has(browserType)) {\n throw new InvalidBrowserTypeError(value);\n }\n return browserType as BrowserType;\n}\n\nfunction parseLaunchMode(value: string): LaunchMode {\n const launchMode = value.toLowerCase();\n if (!SUPPORTED_LAUNCH_MODES.has(launchMode)) {\n throw new InvalidLaunchModeError(value);\n }\n return launchMode as LaunchMode;\n}\n\n/**\n * Parse a comma-separated string into a trimmed, non-empty string array.\n */\nfunction parseCsv(value: string): string[] {\n return value\n .split(CSV_SEPARATOR)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Build a ToolFilterConfig from the raw --tags and --exclude CLI strings.\n * Returns undefined when no filtering is configured.\n */\nfunction buildToolFilter(options: McpServeOptions): ToolFilterConfig | undefined {\n const tags = options.tags ? parseCsv(options.tags) : undefined;\n const exclude = options.exclude ? parseCsv(options.exclude) : undefined;\n\n if (!tags?.length && !exclude?.length) {\n return undefined;\n }\n\n return { tags, exclude };\n}\n\n/**\n * Typed error for browser cleanup failures with structured context.\n */\nexport class BrowserCleanupError extends Error {\n readonly code = 'BROWSER_CLEANUP_FAILED';\n readonly recovery = 'Browsers may still be running; check the HTTP server or kill processes manually';\n readonly failedBrowserIds: string[];\n readonly httpBaseUrl: string;\n\n constructor(failedBrowserIds: string[], httpBaseUrl: string, options?: ErrorOptions) {\n super(`Failed to close ${failedBrowserIds.length} browser(s): ${failedBrowserIds.join(', ')}`, options);\n this.name = 'BrowserCleanupError';\n this.failedBrowserIds = failedBrowserIds;\n this.httpBaseUrl = httpBaseUrl;\n }\n}\n\n/**\n * Typed error for shutdown failures with structured context.\n */\nexport class SessionShutdownError extends Error {\n readonly code = 'SESSION_SHUTDOWN_FAILED';\n readonly recovery = 'Check logs for cleanup and transport errors; processes may need manual cleanup';\n readonly signal: string;\n\n constructor(signal: string, options?: ErrorOptions) {\n super(`Shutdown failed for signal ${signal}`, options);\n this.name = 'SessionShutdownError';\n this.signal = signal;\n }\n}\n\n/**\n * Typed error for HTTP server startup failures.\n */\nexport class HttpServerStartError extends Error {\n readonly code = 'HTTP_SERVER_START_FAILED';\n readonly recovery = 'Check if the HTTP server binary exists and the port is available';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start HTTP server', options);\n this.name = 'HttpServerStartError';\n }\n}\n\n/**\n * Typed error for MCP server bootstrap failures.\n */\nexport class McpServerBootstrapError extends Error {\n readonly code = 'MCP_SERVER_BOOTSTRAP_FAILED';\n readonly recovery = 'Check if all dependencies are installed and try again';\n\n constructor(options?: ErrorOptions) {\n super('Failed to start MCP server', options);\n this.name = 'McpServerBootstrapError';\n }\n}\n\n/**\n * Close browsers tracked in session via HTTP server.\n * Validates HTTP response status before reporting success.\n * Only clears tracker state when all browsers close successfully.\n */\nasync function closeSessionBrowsers(sessionTracker: McpSessionTracker, httpBaseUrl: string): Promise<void> {\n const browserIds = sessionTracker.getBrowserIds();\n const failedIds: string[] = [];\n let firstCause: Error | undefined;\n\n for (const browserId of browserIds) {\n try {\n const response = await fetch(`${httpBaseUrl}/execute`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n tool: BROWSER_CLOSE_TOOL,\n arguments: { browserId },\n }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status} from ${httpBaseUrl}/execute: ${body}`);\n }\n\n console.error(` Closed browser: ${browserId}`);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n console.error(` Failed to close browser: ${browserId}`, cause);\n failedIds.push(browserId);\n firstCause ??= cause;\n }\n }\n\n if (failedIds.length > 0) {\n throw new BrowserCleanupError(failedIds, httpBaseUrl, { cause: firstCause });\n }\n\n sessionTracker.clear();\n}\n\n/**\n * Start MCP server with session-aware cleanup.\n * Uses process.once and an isShuttingDown guard for idempotent shutdown.\n * Always attempts handler.stop() via finally to prevent resource leaks.\n */\nasync function startServerWithSessionCleanup(\n handler: TransportHandler,\n sessionTracker: McpSessionTracker,\n httpBaseUrl: string,\n): Promise<void> {\n await handler.start();\n\n let isShuttingDown = false;\n\n const shutdown = async (signal: string) => {\n if (isShuttingDown) {\n return;\n }\n isShuttingDown = true;\n\n console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n let cleanupError: Error | undefined;\n\n try {\n const state = sessionTracker.getSessionState();\n if (state.totalBrowsers > 0) {\n console.error(` Cleaning up ${state.totalBrowsers} browser(s) from this session...`);\n await closeSessionBrowsers(sessionTracker, httpBaseUrl);\n }\n } catch (error) {\n cleanupError = error instanceof Error ? error : new Error(String(error));\n console.error('Browser cleanup error:', cleanupError);\n } finally {\n try {\n await handler.stop();\n } catch (stopError) {\n const cause = stopError instanceof Error ? stopError : new Error(String(stopError));\n console.error('Transport stop error:', cause);\n cleanupError ??= cause;\n }\n\n if (cleanupError) {\n const shutdownError = new SessionShutdownError(signal, { cause: cleanupError });\n console.error('Error during shutdown:', shutdownError);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n process.exit(EXIT_CODE_SUCCESS);\n }\n };\n\n process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n}\n\n// ============================================================================\n// Command Definition\n// ============================================================================\n\nexport const mcpServeCommand = new Command('mcp-serve')\n .description('Start Playwright MCP server for browser automation')\n .option('-t, --type <type>', `Transport type: ${TRANSPORT_STDIO}`, TRANSPORT_STDIO)\n .option('-b, --browser <browser>', `Default browser type: ${[...SUPPORTED_BROWSERS].join(', ')}`, BROWSER_CHROMIUM)\n .option('--headless', 'Run browsers in headless mode by default', false)\n .option('--no-headless', 'Run browsers in headed mode (visible window)')\n .option('--host <host>', 'Default host for HTTP services', getPlaywrightHost())\n .option('-p, --profile <name>', 'Default profile name for browser sessions')\n .option('-m, --mode <mode>', `Default launch mode: ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`)\n .option('--tags <tags>', 'Comma-separated tags to filter tools (e.g. input,navigation,snapshot)')\n .option('--exclude <tools>', 'Comma-separated tool names to exclude (applied after --tags)')\n .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n .option('--registry-dir <path>', 'Custom registry directory for service discovery')\n .option('--pids-dir <path>', 'Custom PIDs directory for process tracking')\n .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n .action(async function (this: Command, options: McpServeOptions) {\n const commandDefaults = getCommandConfig<{\n type?: string;\n browser?: BrowserType;\n headless?: boolean;\n profile?: string;\n mode?: LaunchMode;\n host?: string;\n tags?: string;\n exclude?: string;\n registryPath?: string;\n registryDir?: string;\n pidsDir?: string;\n profilesDir?: string;\n }>('mcpServe');\n const resolvedOptions: McpServeOptions = {\n type: resolveConfiguredOption(this, 'type', options.type, commandDefaults.type),\n browser: resolveConfiguredOption(this, 'browser', options.browser, commandDefaults.browser),\n headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n profile: resolveConfiguredOption(this, 'profile', options.profile, commandDefaults.profile),\n mode: resolveConfiguredOption(this, 'mode', options.mode, commandDefaults.mode),\n host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n tags: resolveConfiguredOption(this, 'tags', options.tags, commandDefaults.tags),\n exclude: resolveConfiguredOption(this, 'exclude', options.exclude, commandDefaults.exclude),\n registryPath: resolveConfiguredOption(\n this,\n 'registryPath',\n options.registryPath,\n commandDefaults.registryPath,\n process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n ),\n registryDir: resolveConfiguredOption(\n this,\n 'registryDir',\n options.registryDir,\n commandDefaults.registryDir,\n process.env.PLAYWRIGHT_REGISTRY_DIR,\n ),\n pidsDir: resolveConfiguredOption(\n this,\n 'pidsDir',\n options.pidsDir,\n commandDefaults.pidsDir,\n process.env.PLAYWRIGHT_PIDS_DIR,\n ),\n profilesDir: resolveConfiguredOption(\n this,\n 'profilesDir',\n options.profilesDir,\n commandDefaults.profilesDir,\n process.env.PLAYWRIGHT_PROFILES_DIR,\n ),\n };\n const transportType = resolvedOptions.type.toLowerCase();\n\n try {\n if (!SUPPORTED_TRANSPORTS.has(transportType)) {\n throw new InvalidTransportError(transportType);\n }\n\n const browserType = parseBrowserType(resolvedOptions.browser);\n const defaultMode = resolvedOptions.mode ? parseLaunchMode(resolvedOptions.mode) : undefined;\n const toolFilter = buildToolFilter(resolvedOptions);\n\n console.error('Playwright MCP Server starting...');\n console.error(` Transport: ${transportType}`);\n console.error(` Default browser: ${browserType}`);\n console.error(` Headless: ${resolvedOptions.headless}`);\n if (defaultMode) {\n console.error(` Default mode: ${defaultMode}`);\n }\n if (resolvedOptions.profile) {\n console.error(` Profile: ${resolvedOptions.profile}`);\n }\n if (toolFilter?.tags?.length) {\n console.error(` Tags: ${toolFilter.tags.join(', ')}`);\n }\n if (toolFilter?.exclude?.length) {\n console.error(` Exclude: ${toolFilter.exclude.join(', ')}`);\n }\n\n const registryPath = resolvedOptions.registryPath || resolvedOptions.registryDir;\n if (registryPath) {\n process.env[ENV_REGISTRY_DIR] = registryPath;\n process.env[ENV_PORT_REGISTRY_PATH] = registryPath;\n console.error(` Registry path: ${registryPath}`);\n }\n if (resolvedOptions.pidsDir) {\n process.env[ENV_PIDS_DIR] = resolvedOptions.pidsDir;\n console.error(` PIDs dir: ${resolvedOptions.pidsDir}`);\n }\n if (resolvedOptions.profilesDir) {\n process.env[ENV_PROFILES_DIR] = resolvedOptions.profilesDir;\n console.error(` Profiles dir: ${resolvedOptions.profilesDir}`);\n }\n if (resolvedOptions.host) {\n process.env[ENV_HOST] = resolvedOptions.host;\n }\n process.env[ENV_PORT] = getPlaywrightPort().toString();\n\n const localContainer = createMcpContainer();\n\n const httpServerManager = localContainer.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n const httpStatus = await httpServerManager.ensureRunning();\n\n if (!httpStatus.running) {\n throw new HttpServerStartError();\n }\n\n const httpBaseUrl = buildPlaywrightBaseUrl(getPlaywrightHost(), httpStatus.port);\n const httpLabel = httpStatus.spawned ? HTTP_STATUS_SPAWNED : HTTP_STATUS_REUSED;\n console.error(` HTTP server: ${httpLabel} on port ${httpStatus.port}`);\n\n const sessionTracker = new McpSessionTracker();\n\n const server = createProxyServer({ httpBaseUrl, sessionTracker, defaultMode, toolFilter });\n const handler = new StdioTransportHandler(server);\n\n await startServerWithSessionCleanup(handler, sessionTracker, httpBaseUrl);\n } catch (error) {\n if (\n error instanceof InvalidTransportError ||\n error instanceof InvalidBrowserTypeError ||\n error instanceof InvalidLaunchModeError ||\n error instanceof HttpServerStartError\n ) {\n console.error(`Error [${error.code}]: ${error.message}`);\n console.error(`Recovery: ${error.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n const bootstrapError = new McpServerBootstrapError({ cause });\n console.error(`Error [${bootstrapError.code}]: ${bootstrapError.message}`, bootstrapError.cause);\n console.error(`Recovery: ${bootstrapError.recovery}`);\n process.exit(EXIT_CODE_FAILURE);\n }\n });\n","/**\n * Status Command\n *\n * Shows status of HTTP server and other diagnostics.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\n/**\n * Show status of browse-tool services\n */\nexport const statusCommand = new Command('status')\n .description('Show status of HTTP server and diagnostics')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n host?: string;\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('browse-tool Status\\n');\n console.log('-'.repeat(50));\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Get HTTP server status\n const httpStatus = await httpServerManager.getStatus();\n\n if (httpStatus.running) {\n const host = process.env.PLAYWRIGHT_HOST ?? httpServeDefaults.host ?? getPlaywrightHost();\n console.log('HTTP Server: Running');\n console.log(` PID: ${httpStatus.pid}`);\n console.log(` Port: ${httpStatus.port}`);\n console.log(` Health: ${buildPlaywrightBaseUrl(host, httpStatus.port)}/health`);\n if (httpStatus.browserCount !== undefined) {\n console.log(` Browsers: ${httpStatus.browserCount}`);\n }\n } else {\n console.log('HTTP Server: Not Running');\n if (httpStatus.error) {\n console.log(` Error: ${httpStatus.error}`);\n }\n }\n\n console.log('');\n console.log('-'.repeat(50));\n process.exit(0);\n } catch (error) {\n console.error('Error getting status:', error);\n process.exit(1);\n }\n });\n","/**\n * Stop Command\n *\n * Stops HTTP server and cleans up registry/PID files.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\n\n/**\n * Stop HTTP server\n */\nexport const stopCommand = new Command('stop')\n .description('Stop HTTP server and clean up registry/PID files')\n .action(async () => {\n try {\n const httpServeDefaults = getCommandConfig<{\n registryDir?: string;\n registryPath?: string;\n pidsDir?: string;\n }>('httpServe');\n const registryPath =\n process.env.PLAYWRIGHT_REGISTRY_PATH ??\n process.env.PORT_REGISTRY_PATH ??\n httpServeDefaults.registryPath ??\n httpServeDefaults.registryDir;\n\n if (registryPath) {\n process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n process.env.PORT_REGISTRY_PATH = registryPath;\n }\n if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n }\n\n console.log('Stopping browse-tool services...');\n\n // Create container to get HttpServerManager\n const container = createContainer();\n const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n // Stop HTTP server\n const stopped = await httpServerManager.stop();\n\n if (stopped) {\n console.log('HTTP server stopped');\n console.log('Registry and PID files cleaned up');\n } else {\n console.log('No HTTP server was running');\n }\n\n console.log('Done!');\n process.exit(0);\n } catch (error) {\n console.error('Error stopping services:', error);\n process.exit(1);\n }\n });\n","const ROOT_OPTIONS_WITH_VALUES = new Set(['--config']);\n\nexport function getFirstCommandToken(argv: string[]): string | undefined {\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n\n if (ROOT_OPTIONS_WITH_VALUES.has(arg)) {\n index += 1;\n continue;\n }\n\n if ([...ROOT_OPTIONS_WITH_VALUES].some((option) => arg.startsWith(`${option}=`))) {\n continue;\n }\n\n if (arg.startsWith('-')) {\n continue;\n }\n\n return arg;\n }\n\n return undefined;\n}\n\nexport function shouldRegisterDynamicToolCommands(argv: string[], skipDynamicTools = false): boolean {\n if (skipDynamicTools) {\n return false;\n }\n\n const firstCommand = getFirstCommandToken(argv);\n if (!firstCommand) {\n return false;\n }\n\n if (firstCommand === 'tools') {\n return true;\n }\n\n return firstCommand === 'help' && argv.includes('tools');\n}\n","/**\n * Command Builder Utilities for Tool Commands\n *\n * Converts tool definitions with JSON Schema to Commander.js commands.\n * Handles schema-to-CLI option conversion with proper type handling.\n *\n * DESIGN PATTERNS:\n * - Builder pattern for command construction\n * - Schema-driven command generation\n * - Type-safe option parsing\n *\n * CODING STANDARDS:\n * - Use TypeScript for type safety\n * - Handle all JSON Schema types appropriately\n * - Provide clear CLI conventions (kebab-case)\n *\n * AVOID:\n * - Manual command definitions for each tool\n * - Inconsistent naming conventions\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { Command, Option } from 'commander';\nimport { getCommandConfig, getToolConfig, resolveConfiguredOption } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { type FormatterOptions, type OutputFormat, formatError, formatToolResult } from './outputFormatter.js';\n\n/**\n * JSON Schema property definition\n */\ninterface SchemaProperty {\n type?: string;\n description?: string;\n default?: unknown;\n enum?: string[];\n items?: { type?: string; enum?: string[] };\n oneOf?: SchemaProperty[];\n properties?: Record<string, SchemaProperty>;\n additionalProperties?: boolean | SchemaProperty;\n minimum?: number;\n maximum?: number;\n}\n\n/**\n * Global options inherited by all tool commands\n */\ninterface GlobalOptions {\n format: OutputFormat;\n color: boolean;\n port: string;\n}\n\nexport interface ToolCommandContext {\n command: Command;\n formatterOptions: FormatterOptions;\n port: number;\n}\n\nexport type ToolCommandExecutor = (\n args: Record<string, unknown>,\n context: ToolCommandContext,\n) => Promise<CallToolResult>;\n\nexport interface ToolCommandDefinition {\n definition: ToolDefinition;\n execute?: ToolCommandExecutor;\n}\n\n/**\n * Convert snake_case to kebab-case for CLI command names\n */\nexport function toKebabCase(str: string): string {\n return str.replace(/_/g, '-');\n}\n\n/**\n * Convert camelCase to kebab-case for CLI option names\n */\nexport function camelToKebab(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\nfunction getSchemaTypes(schema: SchemaProperty): Set<string> {\n const types = new Set<string>();\n\n if (schema.type) {\n types.add(schema.type);\n }\n\n for (const variant of schema.oneOf ?? []) {\n if (variant.type) {\n types.add(variant.type);\n }\n }\n\n return types;\n}\n\n/**\n * Parse a CLI option value based on its JSON Schema type\n */\nexport function parseOptionValue(value: string, schema: SchemaProperty): unknown {\n const types = getSchemaTypes(schema);\n\n if (schema.type === 'number' || schema.type === 'integer') {\n const num = Number(value);\n if (Number.isNaN(num)) {\n throw new Error(`Invalid number: ${value}`);\n }\n return num;\n }\n\n if (schema.type === 'boolean') {\n if (value === 'true' || value === '1') return true;\n if (value === 'false' || value === '0') return false;\n throw new Error(`Invalid boolean: ${value}`);\n }\n\n if (types.has('array') || types.has('object')) {\n try {\n return JSON.parse(value);\n } catch {\n if (schema.type === 'array' || schema.type === 'object') {\n throw new Error(`Invalid JSON: ${value}`);\n }\n }\n }\n\n if (types.has('number') || types.has('integer')) {\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') {\n return num;\n }\n }\n\n return value;\n}\n\n/**\n * Build Commander option flag string from property name and schema\n */\nexport function buildOptionFlag(name: string, schema: SchemaProperty): string {\n const kebabName = camelToKebab(name);\n\n if (schema.type === 'boolean') {\n return `--${kebabName}`;\n }\n\n return `--${kebabName} <value>`;\n}\n\n/**\n * Build a Commander command from a tool definition\n */\nexport function buildToolCommand(commandDefinition: ToolCommandDefinition): Command {\n const { definition: tool, execute } = commandDefinition;\n const commandName = toKebabCase(tool.name);\n const command = new Command(commandName);\n\n command.description(tool.description);\n\n const schema = tool.inputSchema;\n const properties = schema.properties || {};\n const required = new Set(schema.required || []);\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const flag = buildOptionFlag(propName, prop);\n let description = prop.description || '';\n\n if (prop.enum && prop.enum.length > 0) {\n description += ` (choices: ${prop.enum.join(', ')})`;\n }\n\n if (prop.default !== undefined) {\n description += ` (default: ${JSON.stringify(prop.default)})`;\n }\n\n if (required.has(propName)) {\n description += ' [required]';\n }\n\n if (prop.type === 'boolean') {\n if (prop.default === true) {\n command.option(`--no-${camelToKebab(propName)}`, `Disable ${description}`);\n } else {\n command.option(flag, description, prop.default as boolean);\n }\n } else if (prop.enum && prop.enum.length > 0) {\n command.addOption(new Option(flag, description).choices(prop.enum));\n } else {\n command.option(flag, description);\n }\n }\n\n command.action(async function (this: Command) {\n const options = this.optsWithGlobals() as Record<string, unknown> & GlobalOptions;\n const commandDefaults = getCommandConfig<Partial<GlobalOptions>>('tools');\n const format = resolveConfiguredOption(\n this,\n 'format',\n (options.format ?? 'json') as OutputFormat,\n commandDefaults.format,\n );\n const color = resolveConfiguredOption(this, 'color', options.color ?? true, commandDefaults.color);\n const portValue = resolveConfiguredOption(\n this,\n 'port',\n String(options.port ?? DEFAULT_MCP_PORT),\n commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n process.env.PLAYWRIGHT_PORT,\n );\n const formatterOptions: FormatterOptions = { format, color };\n const toolDefaults = getToolConfig(tool.name);\n\n try {\n const args: Record<string, unknown> = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const prop = propSchema as SchemaProperty;\n const kebabName = camelToKebab(propName);\n const optionKey = kebabName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n const optionSource = this.getOptionValueSourceWithGlobals(optionKey);\n let value = options[optionKey];\n\n if (prop.type === 'boolean' && prop.default === true) {\n value = options[optionKey];\n }\n\n if (\n (optionSource === undefined || optionSource === 'default' || optionSource === 'implied') &&\n toolDefaults[propName] !== undefined\n ) {\n value = toolDefaults[propName];\n }\n\n if (value !== undefined) {\n if (typeof value === 'string' && prop.type !== 'string') {\n args[propName] = parseOptionValue(value, prop);\n } else {\n args[propName] = value;\n }\n }\n }\n\n for (const reqField of required) {\n if (args[reqField] === undefined) {\n const kebabField = camelToKebab(reqField);\n console.error(formatError(`Missing required option: --${kebabField}`, formatterOptions.color));\n process.exit(1);\n }\n }\n\n const port = Number.parseInt(portValue, 10);\n const client = createToolClient({ port });\n const result = execute\n ? await execute(args, { command: this, formatterOptions, port })\n : await client.execute(tool.name, args);\n const output = formatToolResult(result, formatterOptions);\n\n if (output) {\n console.log(output);\n }\n\n if (result.isError) {\n process.exit(1);\n }\n } catch (error) {\n console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n process.exit(1);\n }\n });\n\n return command;\n}\n\n/**\n * Build commands for all tools from definitions\n */\nexport function buildAllToolCommands(tools: ToolCommandDefinition[]): Command[] {\n return tools.map(buildToolCommand);\n}\n","/**\n * Tool Commands Registration Utilities\n *\n * Dynamically creates CLI commands from tool definitions fetched from the HTTP server.\n * Adds CLI-only shims for MCP capabilities that are not directly exposed as HTTP tools.\n *\n * DESIGN PATTERNS:\n * - Factory pattern for command creation\n * - Pure functions for data transformation\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - Hardcoded tool definitions\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { type ToolCommandDefinition, buildAllToolCommands, toKebabCase } from './commandBuilder.js';\nimport type { BrowserInfo } from './httpClient.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { formatError } from './outputFormatter.js';\n\nconst SESSION_TOOL_NAME = 'browser_list_session';\nconst SESSION_TOOL_DESCRIPTION = 'List browsers and pages currently tracked by the browse-tool HTTP server.';\n\n/**\n * Tool category groupings for help organization\n */\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n browser: ['browser_launch', 'browser_close', 'browser_list_pages', 'browser_resize_page', SESSION_TOOL_NAME],\n navigation: ['browser_navigate', 'browser_go_back', 'browser_go_forward', 'browser_reload', 'browser_wait_for'],\n interaction: [\n 'browser_click',\n 'browser_type',\n 'browser_fill',\n 'browser_select',\n 'browser_hover',\n 'browser_press_key',\n 'browser_drag',\n 'browser_upload_file',\n ],\n extraction: ['browser_snapshot', 'browser_screenshot', 'browser_pdf'],\n script: ['browser_evaluate_script', 'browser_run_code'],\n network: ['browser_list_network_requests', 'browser_get_network_request'],\n dialog: ['browser_handle_dialog'],\n tracing: ['browser_start_trace', 'browser_stop_trace'],\n testing: ['browser_expect', 'run_spec', 'discover_specs'],\n profile: ['browser_list_profiles', 'browser_delete_profile'],\n};\n\nconst SESSION_TOOL_DEFINITION: ToolDefinition = {\n name: SESSION_TOOL_NAME,\n description: SESSION_TOOL_DESCRIPTION,\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n};\n\n/**\n * Get category for a tool name\n */\nexport function getToolCategory(toolName: string): string {\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n if (tools.includes(toolName)) {\n return category;\n }\n }\n return 'other';\n}\n\n/**\n * Create a parent command for tool commands with global options\n */\nexport function createToolsCommand(): Command {\n return new Command('tools')\n .description('Execute browser automation tools')\n .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n .option('--no-color', 'Disable colored output')\n .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT));\n}\n\nfunction formatSessionResult(browsers: BrowserInfo[]) {\n return {\n browserCount: browsers.length,\n browsers: browsers.map((browser) => ({\n browserId: browser.id,\n profileName: browser.profileName,\n currentPageId: browser.currentPageId,\n pageIds: browser.pageIds,\n createdAt: browser.createdAt,\n })),\n };\n}\n\nfunction createSessionToolCommandDefinition(): ToolCommandDefinition {\n return {\n definition: SESSION_TOOL_DEFINITION,\n execute: async (_args, context) => {\n const client = createToolClient({ port: context.port });\n const browsers = await client.listBrowsers();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(formatSessionResult(browsers), null, 2),\n },\n ],\n };\n },\n };\n}\n\nfunction buildCommandDefinitions(tools: ToolDefinition[]): ToolCommandDefinition[] {\n return [...tools.map((definition) => ({ definition })), createSessionToolCommandDefinition()];\n}\n\n/**\n * Register tool commands under a parent command\n * Fetches tool definitions from the HTTP server and creates corresponding CLI commands\n */\nexport async function registerToolCommands(parentCommand: Command, options?: { port?: number }): Promise<void> {\n const commandDefaults = getCommandConfig<{ port?: number }>('tools');\n const port = Number(options?.port ?? process.env.PLAYWRIGHT_PORT ?? commandDefaults.port ?? DEFAULT_MCP_PORT);\n\n try {\n const client = createToolClient({ port });\n const tools = await client.listTools();\n const toolCommands = buildAllToolCommands(buildCommandDefinitions(tools));\n\n for (const cmd of toolCommands) {\n parentCommand.addCommand(cmd);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${formatError(`Failed to load tool commands: ${errorMessage}`, true)}\\n`);\n process.stderr.write('Make sure the HTTP server is running: browse-tool http-serve\\n');\n }\n}\n\n/**\n * Create tool commands from static definitions (for offline use)\n */\nexport function createToolCommandsFromDefinitions(tools: ToolDefinition[]): Command[] {\n return buildAllToolCommands(buildCommandDefinitions(tools));\n}\n\n/**\n * Get help text for tool categories\n */\nexport function getCategoryHelp(): string {\n const lines: string[] = ['Tool Categories:'];\n\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n const kebabTools = tools.map(toKebabCase).join(', ');\n lines.push(` ${category}: ${kebabTools}`);\n }\n\n return lines.join('\\n');\n}\n","#!/usr/bin/env node\nimport 'reflect-metadata/lite';\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { chromeServeCommand } from './commands/chrome-serve.js';\nimport { execCommand } from './commands/exec.js';\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { initializeCliRuntime } from './config.js';\nimport { shouldRegisterDynamicToolCommands } from './utils/cliArgs.js';\nimport { DEFAULT_MCP_PORT } from './utils/networkConfig.js';\nimport { createToolsCommand, registerToolCommands } from './utils/toolCommands.js';\n\nconst SKIP_DYNAMIC_TOOLS_ENV = 'PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS';\n\n/**\n * Main entry point\n */\nasync function main() {\n const runtime = initializeCliRuntime(process.argv.slice(2));\n const configuredToolsPort =\n process.env.PLAYWRIGHT_PORT ??\n runtime.config.commands.tools?.port ??\n runtime.config.commands.exec?.port ??\n DEFAULT_MCP_PORT;\n const program = new Command();\n\n program\n .name('browse-tool')\n .description(\n 'MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support',\n )\n .option('--config <path>', 'Path to browse-tool config file or config directory')\n .version(packageJson.version);\n\n // Add all commands\n program.addCommand(mcpServeCommand);\n program.addCommand(httpServeCommand);\n program.addCommand(chromeServeCommand);\n program.addCommand(stopCommand);\n program.addCommand(statusCommand);\n program.addCommand(execCommand);\n\n // Create and register tool commands (dynamically generated from HTTP server)\n const toolsCommand = createToolsCommand();\n if (shouldRegisterDynamicToolCommands(process.argv.slice(2), process.env[SKIP_DYNAMIC_TOOLS_ENV] === '1')) {\n await registerToolCommands(toolsCommand, { port: Number(configuredToolsPort) });\n }\n program.addCommand(toolsCommand);\n\n // Parse arguments\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}