diff --git a/src/DEPS.list b/src/DEPS.list index c6b6794..c1e18f2 100644 --- a/src/DEPS.list +++ b/src/DEPS.list @@ -1,6 +1,7 @@ [*] ./tools/ ./mcp/ +./utils/ [program.ts] *** diff --git a/src/browserContextFactory.ts b/src/browserContextFactory.ts index 9646bc5..ecc835d 100644 --- a/src/browserContextFactory.ts +++ b/src/browserContextFactory.ts @@ -23,8 +23,8 @@ import * as playwright from 'playwright'; import { registryDirectory } from 'playwright-core/lib/server/registry/index'; // @ts-ignore import { startTraceViewerServer } from 'playwright-core/lib/server'; -import { logUnhandledError, testDebug } from './log.js'; -import { createHash } from './utils.js'; +import { logUnhandledError, testDebug } from './utils/log.js'; +import { createHash } from './utils/guid.js'; import { outputFile } from './config.js'; import type { FullConfig } from './config.js'; diff --git a/src/browserServerBackend.ts b/src/browserServerBackend.ts index b840d84..4a6b1f6 100644 --- a/src/browserServerBackend.ts +++ b/src/browserServerBackend.ts @@ -17,11 +17,11 @@ import { fileURLToPath } from 'url'; import { FullConfig } from './config.js'; import { Context } from './context.js'; -import { logUnhandledError } from './log.js'; +import { logUnhandledError } from './utils/log.js'; import { Response } from './response.js'; import { SessionLog } from './sessionLog.js'; import { filteredTools } from './tools.js'; -import { packageJSON } from './package.js'; +import { packageJSON } from './utils/package.js'; import { toToolDefinition } from './tools/tool.js'; import type { Tool } from './tools/tool.js'; diff --git a/src/config.ts b/src/config.ts index 2dddac5..e579002 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import { devices } from 'playwright'; -import { sanitizeForFilePath } from './utils.js'; +import { sanitizeForFilePath } from './utils/fileUtils.js'; import type { Config, ToolCapability } from '../config.js'; import type { BrowserContextOptions, LaunchOptions } from 'playwright'; diff --git a/src/context.ts b/src/context.ts index e84356d..52ebb59 100644 --- a/src/context.ts +++ b/src/context.ts @@ -17,7 +17,7 @@ import debug from 'debug'; import * as playwright from 'playwright'; -import { logUnhandledError } from './log.js'; +import { logUnhandledError } from './utils/log.js'; import { Tab } from './tab.js'; import { outputFile } from './config.js'; diff --git a/src/extension/DEPS.list b/src/extension/DEPS.list index 69e525a..796b333 100644 --- a/src/extension/DEPS.list +++ b/src/extension/DEPS.list @@ -1,3 +1,3 @@ [*] -../ ../mcp/ +../utils/ diff --git a/src/extension/cdpRelay.ts b/src/extension/cdpRelay.ts index dc99fc0..53e639e 100644 --- a/src/extension/cdpRelay.ts +++ b/src/extension/cdpRelay.ts @@ -26,9 +26,9 @@ import { spawn } from 'child_process'; import http from 'http'; import debug from 'debug'; import { WebSocket, WebSocketServer } from 'ws'; -import { httpAddressToString } from '../httpServer.js'; -import { logUnhandledError } from '../log.js'; -import { ManualPromise } from '../manualPromise.js'; +import { httpAddressToString } from '../utils/httpServer.js'; +import { logUnhandledError } from '../utils/log.js'; +import { ManualPromise } from '../utils/manualPromise.js'; import type websocket from 'ws'; import type { ClientInfo } from '../browserContextFactory.js'; diff --git a/src/extension/extensionContextFactory.ts b/src/extension/extensionContextFactory.ts index 95a4f72..7bdfaa0 100644 --- a/src/extension/extensionContextFactory.ts +++ b/src/extension/extensionContextFactory.ts @@ -16,7 +16,7 @@ import debug from 'debug'; import * as playwright from 'playwright'; -import { startHttpServer } from '../httpServer.js'; +import { startHttpServer } from '../utils/httpServer.js'; import { CDPRelayServer } from './cdpRelay.js'; import type { BrowserContextFactory, ClientInfo } from '../browserContextFactory.js'; diff --git a/src/extension/main.ts b/src/extension/main.ts deleted file mode 100644 index f9fa177..0000000 --- a/src/extension/main.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ExtensionContextFactory } from './extensionContextFactory.js'; -import { BrowserServerBackend } from '../browserServerBackend.js'; -import { InProcessClientFactory } from '../inProcessClient.js'; -import * as mcpTransport from '../mcp/transport.js'; - -import type { FullConfig } from '../config.js'; -import type { ClientFactory } from '../mcp/proxyBackend.js'; - -export async function runWithExtension(config: FullConfig) { - const contextFactory = createExtensionContextFactory(config); - const serverBackendFactory = () => new BrowserServerBackend(config, contextFactory); - await mcpTransport.start(serverBackendFactory, config.server); -} - -export function createExtensionClientFactory(config: FullConfig): ClientFactory { - return new InProcessClientFactory(createExtensionContextFactory(config), config); -} - - -function createExtensionContextFactory(config: FullConfig) { - return new ExtensionContextFactory(config.browser.launchOptions.channel || 'chrome', config.browser.userDataDir); -} diff --git a/src/loopTools/DEPS.list b/src/loopTools/DEPS.list index 07786f2..9fe8b14 100644 --- a/src/loopTools/DEPS.list +++ b/src/loopTools/DEPS.list @@ -3,3 +3,4 @@ ../loop/ ../mcp/ ../tools/ +../utils/ diff --git a/src/loopTools/main.ts b/src/loopTools/main.ts index 3d9f346..7943fe3 100644 --- a/src/loopTools/main.ts +++ b/src/loopTools/main.ts @@ -18,7 +18,7 @@ import dotenv from 'dotenv'; import * as mcpServer from '../mcp/server.js'; import * as mcpTransport from '../mcp/transport.js'; -import { packageJSON } from '../package.js'; +import { packageJSON } from '../utils/package.js'; import { Context } from './context.js'; import { perform } from './perform.js'; import { snapshot } from './snapshot.js'; diff --git a/src/mcp/DEPS.list b/src/mcp/DEPS.list index 9f8ba35..5870e2d 100644 --- a/src/mcp/DEPS.list +++ b/src/mcp/DEPS.list @@ -1,7 +1,2 @@ [*] -../log.js -../manualPromise.js -../httpServer.js - -[proxyBackend.ts] -../package.js \ No newline at end of file +../utils/ diff --git a/src/mcp/proxyBackend.ts b/src/mcp/proxyBackend.ts index 983c833..1128818 100644 --- a/src/mcp/proxyBackend.ts +++ b/src/mcp/proxyBackend.ts @@ -18,8 +18,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; -import { logUnhandledError } from '../log.js'; -import { packageJSON } from '../package.js'; +import { logUnhandledError } from '../utils/log.js'; +import { packageJSON } from '../utils/package.js'; import { ToolDefinition, ServerBackend, ToolResponse } from './server.js'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 6f06d82..39ae9f9 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -17,8 +17,8 @@ import debug from 'debug'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { ManualPromise } from '../manualPromise.js'; -import { logUnhandledError } from '../log.js'; +import { ManualPromise } from '../utils/manualPromise.js'; +import { logUnhandledError } from '../utils/log.js'; import type { ImageContent, TextContent, Tool } from '@modelcontextprotocol/sdk/types.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; diff --git a/src/mcp/transport.ts b/src/mcp/transport.ts index 9d82b40..06965b7 100644 --- a/src/mcp/transport.ts +++ b/src/mcp/transport.ts @@ -21,7 +21,7 @@ import debug from 'debug'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { httpAddressToString, startHttpServer } from '../httpServer.js'; +import { httpAddressToString, startHttpServer } from '../utils/httpServer.js'; import * as mcpServer from './server.js'; import type { ServerBackendFactory } from './server.js'; diff --git a/src/program.ts b/src/program.ts index 13d9e32..2214c47 100644 --- a/src/program.ts +++ b/src/program.ts @@ -18,17 +18,18 @@ import { program, Option } from 'commander'; import * as mcpTransport from './mcp/transport.js'; import { commaSeparatedList, resolveCLIConfig, semicolonSeparatedList } from './config.js'; -import { packageJSON } from './package.js'; -import { createExtensionClientFactory, runWithExtension } from './extension/main.js'; +import { packageJSON } from './utils/package.js'; import { Context } from './context.js'; import { contextFactory } from './browserContextFactory.js'; import { runLoopTools } from './loopTools/main.js'; import { ProxyBackend } from './mcp/proxyBackend.js'; import { InProcessClientFactory } from './inProcessClient.js'; import { BrowserServerBackend } from './browserServerBackend.js'; +import { ExtensionContextFactory } from './extension/extensionContextFactory.js'; import type { ClientFactoryList } from './mcp/proxyBackend.js'; import type { ServerBackendFactory } from './mcp/server.js'; +import type { FullConfig } from './config.js'; program .version('Version ' + packageJSON.version) @@ -73,7 +74,9 @@ program const config = await resolveCLIConfig(options); if (options.extension) { - await runWithExtension(config); + const contextFactory = createExtensionContextFactory(config); + const serverBackendFactory = () => new BrowserServerBackend(config, contextFactory); + await mcpTransport.start(serverBackendFactory, config.server); return; } if (options.loopTools) { @@ -86,7 +89,7 @@ program if (options.connectTool) { const factories: ClientFactoryList = [ new InProcessClientFactory(browserContextFactory, config), - createExtensionClientFactory(config) + new InProcessClientFactory(createExtensionContextFactory(config), config), ]; serverBackendFactory = () => new ProxyBackend(factories); } else { @@ -111,4 +114,8 @@ function setupExitWatchdog() { process.on('SIGTERM', handleExit); } +function createExtensionContextFactory(config: FullConfig) { + return new ExtensionContextFactory(config.browser.launchOptions.channel || 'chrome', config.browser.userDataDir); +} + void program.parseAsync(process.argv); diff --git a/src/sessionLog.ts b/src/sessionLog.ts index ba841b4..dfda6a0 100644 --- a/src/sessionLog.ts +++ b/src/sessionLog.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import path from 'path'; import { Response } from './response.js'; -import { logUnhandledError } from './log.js'; +import { logUnhandledError } from './utils/log.js'; import { outputFile } from './config.js'; import type { FullConfig } from './config.js'; diff --git a/src/tab.ts b/src/tab.ts index d7f44fa..1b3fff0 100644 --- a/src/tab.ts +++ b/src/tab.ts @@ -17,8 +17,8 @@ import { EventEmitter } from 'events'; import * as playwright from 'playwright'; import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js'; -import { logUnhandledError } from './log.js'; -import { ManualPromise } from './manualPromise.js'; +import { logUnhandledError } from './utils/log.js'; +import { ManualPromise } from './utils/manualPromise.js'; import { ModalState } from './tools/tool.js'; import type { Context } from './context.js'; diff --git a/src/tools/DEPS.list b/src/tools/DEPS.list index 2467fe7..5870e2d 100644 --- a/src/tools/DEPS.list +++ b/src/tools/DEPS.list @@ -1,4 +1,2 @@ [*] -../javascript.js -../log.js -../manualPromise.js +../utils/ diff --git a/src/tools/evaluate.ts b/src/tools/evaluate.ts index 3023f39..9e4adff 100644 --- a/src/tools/evaluate.ts +++ b/src/tools/evaluate.ts @@ -17,7 +17,7 @@ import { z } from 'zod'; import { defineTabTool } from './tool.js'; -import * as javascript from '../javascript.js'; +import * as javascript from '../utils/codegen.js'; import { generateLocator } from './utils.js'; import type * as playwright from 'playwright'; diff --git a/src/tools/keyboard.ts b/src/tools/keyboard.ts index 12e6e45..35d0016 100644 --- a/src/tools/keyboard.ts +++ b/src/tools/keyboard.ts @@ -19,7 +19,7 @@ import { z } from 'zod'; import { defineTabTool } from './tool.js'; import { elementSchema } from './snapshot.js'; import { generateLocator } from './utils.js'; -import * as javascript from '../javascript.js'; +import * as javascript from '../utils/codegen.js'; const pressKey = defineTabTool({ capability: 'core', diff --git a/src/tools/pdf.ts b/src/tools/pdf.ts index 3de0092..d57aac6 100644 --- a/src/tools/pdf.ts +++ b/src/tools/pdf.ts @@ -17,7 +17,7 @@ import { z } from 'zod'; import { defineTabTool } from './tool.js'; -import * as javascript from '../javascript.js'; +import * as javascript from '../utils/codegen.js'; const pdfSchema = z.object({ filename: z.string().optional().describe('File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.'), diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index 0e4b4ce..e7297ae 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -17,7 +17,7 @@ import { z } from 'zod'; import { defineTabTool } from './tool.js'; -import * as javascript from '../javascript.js'; +import * as javascript from '../utils/codegen.js'; import { generateLocator } from './utils.js'; import type * as playwright from 'playwright'; diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index e8694d9..0a8717d 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -17,7 +17,7 @@ import { z } from 'zod'; import { defineTabTool, defineTool } from './tool.js'; -import * as javascript from '../javascript.js'; +import * as javascript from '../utils/codegen.js'; import { generateLocator } from './utils.js'; const snapshot = defineTool({ diff --git a/src/javascript.ts b/src/utils/codegen.ts similarity index 100% rename from src/javascript.ts rename to src/utils/codegen.ts diff --git a/src/fileUtils.ts b/src/utils/fileUtils.ts similarity index 78% rename from src/fileUtils.ts rename to src/utils/fileUtils.ts index 4155b74..4ebf7e5 100644 --- a/src/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -17,7 +17,7 @@ import os from 'node:os'; import path from 'node:path'; -import type { FullConfig } from './config.js'; +import type { FullConfig } from '../config.js'; export function cacheDir() { let cacheDirectory: string; @@ -35,3 +35,11 @@ export function cacheDir() { export async function userDataDir(browserConfig: FullConfig['browser']) { return path.join(cacheDir(), 'ms-playwright', `mcp-${browserConfig.launchOptions?.channel ?? browserConfig?.browserName}-profile`); } + +export function sanitizeForFilePath(s: string) { + const sanitize = (s: string) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); + const separator = s.lastIndexOf('.'); + if (separator === -1) + return sanitize(s); + return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1)); +} diff --git a/src/utils.ts b/src/utils/guid.ts similarity index 69% rename from src/utils.ts rename to src/utils/guid.ts index 6d1feb0..391d0b6 100644 --- a/src/utils.ts +++ b/src/utils/guid.ts @@ -19,11 +19,3 @@ import crypto from 'crypto'; export function createHash(data: string): string { return crypto.createHash('sha256').update(data).digest('hex').slice(0, 7); } - -export function sanitizeForFilePath(s: string) { - const sanitize = (s: string) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); - const separator = s.lastIndexOf('.'); - if (separator === -1) - return sanitize(s); - return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1)); -} diff --git a/src/httpServer.ts b/src/utils/httpServer.ts similarity index 100% rename from src/httpServer.ts rename to src/utils/httpServer.ts diff --git a/src/log.ts b/src/utils/log.ts similarity index 100% rename from src/log.ts rename to src/utils/log.ts diff --git a/src/manualPromise.ts b/src/utils/manualPromise.ts similarity index 100% rename from src/manualPromise.ts rename to src/utils/manualPromise.ts diff --git a/src/package.ts b/src/utils/package.ts similarity index 92% rename from src/package.ts rename to src/utils/package.ts index e599f68..e3c4bba 100644 --- a/src/package.ts +++ b/src/utils/package.ts @@ -19,4 +19,4 @@ import path from 'path'; import url from 'url'; const __filename = url.fileURLToPath(import.meta.url); -export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', 'package.json'), 'utf8')); +export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', '..', 'package.json'), 'utf8')); diff --git a/tests/roots.spec.ts b/tests/roots.spec.ts index 1529aff..0032d8b 100644 --- a/tests/roots.spec.ts +++ b/tests/roots.spec.ts @@ -19,7 +19,7 @@ import path from 'path'; import { pathToFileURL } from 'url'; import { test, expect } from './fixtures.js'; -import { createHash } from '../src/utils.js'; +import { createHash } from '../src/utils/guid.js'; const p = process.platform === 'win32' ? 'c:\\non\\existent\\folder' : '/non/existent/folder';