diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94b3e88..36a68ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: - run: "./etc/scripts/workflows/ci.ts" - name: Upload target directory + if: always() uses: actions/upload-artifact@v6 with: name: target-directory diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 02dafd1..8066636 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -36,6 +36,7 @@ jobs: - run: "./etc/scripts/workflows/ci.ts" - name: Upload target directory + if: always() uses: actions/upload-artifact@v6 with: name: target-directory diff --git a/.moon/tasks/tag-playwright.yml b/.moon/tasks/tag-playwright.yml index 7da2c5a..2df37f9 100644 --- a/.moon/tasks/tag-playwright.yml +++ b/.moon/tasks/tag-playwright.yml @@ -7,17 +7,20 @@ fileGroups: - playwright.config.ts playwright-browsers: - $workspaceRoot/.tmp/playwright/browsers/**/* - + playwright-workspace-sources: + - $workspaceRoot/**/src/**/* tasks: playwright-test: toolchain: node env: PLAYWRIGHT_BROWSERS_PATH: $workspaceRoot/.tmp/playwright/browsers + NODE_OPTIONS: "--require @swc-node/register" script: >- playwright test inputs: - "@group(playwright-tests)" - "@group(playwright-configs)" + - "@group(playwright-workspace-sources)" playwright-show-report: toolchain: node preset: server diff --git a/apps/ext-e2e/.swcrc b/apps/ext-e2e/.swcrc new file mode 100644 index 0000000..c8732e2 --- /dev/null +++ b/apps/ext-e2e/.swcrc @@ -0,0 +1,21 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false, + "decorators": true, + "dynamicImport": true + }, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "target": "es2022", + "keepClassNames": true + }, + "module": { + "type": "es6" + } +} + + diff --git a/apps/ext-e2e/moon.yml b/apps/ext-e2e/moon.yml index 4c5e342..f4cb83d 100644 --- a/apps/ext-e2e/moon.yml +++ b/apps/ext-e2e/moon.yml @@ -9,3 +9,11 @@ tags: dependsOn: - core-utils - core-server +tasks: + test: + options: + runDepsInParallel: false + deps: + - m2:build + - m3:build + - ~:playwright-test diff --git a/apps/ext-e2e/playwright.config.ts b/apps/ext-e2e/playwright.config.ts index 31035a8..91bc31d 100644 --- a/apps/ext-e2e/playwright.config.ts +++ b/apps/ext-e2e/playwright.config.ts @@ -1,8 +1,10 @@ import { defineConfig, devices } from "@playwright/test"; -import type { ExtContextOptions } from "./fixtures/ext-context"; +import type { ExtContextOptions } from "./src/fixtures/ext-context"; + +process.env.NODE_OPTIONS = "--require @swc-node/register"; export default defineConfig({ - testDir: "./tests", + testDir: "./src/tests", outputDir: "target/playwright/test-results", fullyParallel: true, forbidOnly: !!process.env.CI, @@ -14,6 +16,10 @@ export default defineConfig({ video: "on", }, + build: { + external: [], + }, + projects: [ { name: "m3", diff --git a/apps/ext-e2e/fixtures/ext-context.ts b/apps/ext-e2e/src/fixtures/ext-context.ts similarity index 93% rename from apps/ext-e2e/fixtures/ext-context.ts rename to apps/ext-e2e/src/fixtures/ext-context.ts index 53f52ee..285f0e1 100644 --- a/apps/ext-e2e/fixtures/ext-context.ts +++ b/apps/ext-e2e/src/fixtures/ext-context.ts @@ -4,7 +4,7 @@ import path from 'path'; export type ExtTarget = 'm2' | 'm3'; function getExtensionPath(target: ExtTarget): string { - const basePath = path.join(__dirname, '..', '..', target, 'target', 'extension', 'tmp', 'extension'); + const basePath = path.join(__dirname, '..', '..', '..', target, 'target', 'extension', 'tmp', 'extension'); return basePath; } @@ -22,7 +22,7 @@ export const extContextTest = base.extend context: async ({ extTarget, launchOptions }, use) => { const pathToExtension = getExtensionPath(extTarget); - + const context = await chromium.launchPersistentContext('', { ...launchOptions, channel: 'chromium', @@ -32,11 +32,11 @@ export const extContextTest = base.extend `--load-extension=${pathToExtension}`, ], }); - + await use(context); await context.close(); }, - + extensionId: async ({ context, extTarget }, use) => { if (extTarget === 'm3') { let [serviceWorker] = context.serviceWorkers(); diff --git a/apps/ext-e2e/fixtures/ext-test.ts b/apps/ext-e2e/src/fixtures/ext-test.ts similarity index 100% rename from apps/ext-e2e/fixtures/ext-test.ts rename to apps/ext-e2e/src/fixtures/ext-test.ts diff --git a/apps/ext-e2e/pages/base-page.ts b/apps/ext-e2e/src/pages/base-page.ts similarity index 100% rename from apps/ext-e2e/pages/base-page.ts rename to apps/ext-e2e/src/pages/base-page.ts diff --git a/apps/ext-e2e/pages/index.ts b/apps/ext-e2e/src/pages/index.ts similarity index 99% rename from apps/ext-e2e/pages/index.ts rename to apps/ext-e2e/src/pages/index.ts index 9819d76..056a10c 100644 --- a/apps/ext-e2e/pages/index.ts +++ b/apps/ext-e2e/src/pages/index.ts @@ -1,3 +1,4 @@ export { BasePage } from "./base-page"; export { McpClientPageObject } from "./mcp-client-page-object"; export { PlaywrightPage } from "./playwright-page"; + diff --git a/apps/ext-e2e/pages/mcp-client-page-object.ts b/apps/ext-e2e/src/pages/mcp-client-page-object.ts similarity index 83% rename from apps/ext-e2e/pages/mcp-client-page-object.ts rename to apps/ext-e2e/src/pages/mcp-client-page-object.ts index c02c74f..9a8c6bf 100644 --- a/apps/ext-e2e/pages/mcp-client-page-object.ts +++ b/apps/ext-e2e/src/pages/mcp-client-page-object.ts @@ -4,12 +4,20 @@ import { ListToolsResultSchema, CallToolResultSchema, ListResourcesResultSchema, + type CallToolResult, } from "@modelcontextprotocol/sdk/types.js"; import type { ServerToolName, ServerToolArgs, ServerToolResult, } from "@mcp-browser-kit/core-server"; + +export type TypedCallToolResult = Omit< + CallToolResult, + "structuredContent" +> & { + structuredContent?: ServerToolResult; +}; import { createCoreServerContainer, LoggerFactoryOutputPort, @@ -97,7 +105,7 @@ export class McpClientPageObject { : [ args: ServerToolArgs, ] - ): Promise> { + ): Promise> { const res = await this.client.request( { method: "tools/call", @@ -108,7 +116,7 @@ export class McpClientPageObject { }, CallToolResultSchema, ); - return res.content as ServerToolResult; + return res as TypedCallToolResult; } async listResources() { @@ -121,4 +129,12 @@ export class McpClientPageObject { ); return res.resources; } + + async waitForBrowsers(timeout = 20000) { + const { expect } = await import("@playwright/test"); + await expect(async () => { + const contextOutput = await this.callTool("getContext", {}); + expect(contextOutput.structuredContent?.browsers?.length).toBeGreaterThan(0); + }).toPass({ timeout, intervals: [2000] }); + } } diff --git a/apps/ext-e2e/pages/playwright-page.ts b/apps/ext-e2e/src/pages/playwright-page.ts similarity index 100% rename from apps/ext-e2e/pages/playwright-page.ts rename to apps/ext-e2e/src/pages/playwright-page.ts diff --git a/apps/ext-e2e/src/tests/mcp-connection.spec.ts b/apps/ext-e2e/src/tests/mcp-connection.spec.ts new file mode 100644 index 0000000..333c58f --- /dev/null +++ b/apps/ext-e2e/src/tests/mcp-connection.spec.ts @@ -0,0 +1,8 @@ +import { test } from "../fixtures/ext-test"; + +test("establishes MCP server connection and initializes browsers", async ({ playwrightPage, mcpClientPage }) => { + await playwrightPage.navigate(); + await mcpClientPage.startServer(); + await mcpClientPage.connect(); + await mcpClientPage.waitForBrowsers(); +}); diff --git a/apps/ext-e2e/tests/example.spec.ts b/apps/ext-e2e/tests/example.spec.ts deleted file mode 100644 index 7189133..0000000 --- a/apps/ext-e2e/tests/example.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { test, expect } from '../fixtures/ext-test'; - -test('has title', async ({ playwrightPage }) => { - await playwrightPage.navigate(); - await expect(playwrightPage.page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ playwrightPage }) => { - await playwrightPage.navigate(); - await playwrightPage.clickGetStarted(); - await expect(playwrightPage.installationHeading).toBeVisible(); -}); diff --git a/apps/ext-e2e/tsconfig.json b/apps/ext-e2e/tsconfig.json index 6aef454..bf165e4 100644 --- a/apps/ext-e2e/tsconfig.json +++ b/apps/ext-e2e/tsconfig.json @@ -42,8 +42,7 @@ "outDir": "../../.moon/cache/types/apps/ext-e2e" }, "include": [ - "src/**/*", - "pages/**/*" + "src/**/*" ], "references": [ { diff --git a/etc/scripts/actions/ci.ts b/etc/scripts/actions/ci.ts index 827dc83..3d0877b 100755 --- a/etc/scripts/actions/ci.ts +++ b/etc/scripts/actions/ci.ts @@ -14,7 +14,9 @@ if (fs.existsSync(".git/shallow")) { await $`rm -rf .git/shallow`; } -await pipeOutput($`moon run scripts:playwright-install`); -await pipeOutput($`moon ci`); - -await collectWorkspaceTargets(); +try { + await pipeOutput($`moon run scripts:playwright-install`); + await pipeOutput($`moon ci`); +} finally { + await collectWorkspaceTargets(); +} diff --git a/package.json b/package.json index d9ab37a..b120d7a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "@mozilla/readability": "^0.6.0", "@playwright/test": "^1.57.0", "@readme/data-urls": "^3.0.0", + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8", "@trpc/client": "^11.7.2", "@trpc/server": "^11.7.2", "@types/data-urls": "^3.0.4", diff --git a/packages/server-driving-mcp-server/src/services/browser-tools.ts b/packages/server-driving-mcp-server/src/services/browser-tools.ts index ccdf38f..46352f8 100644 --- a/packages/server-driving-mcp-server/src/services/browser-tools.ts +++ b/packages/server-driving-mcp-server/src/services/browser-tools.ts @@ -11,6 +11,7 @@ import { import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { inject, injectable } from "inversify"; import { over } from "ok-value-error-reason"; +import { registerTool } from "../utils/register-tool"; import { createErrorResponse, createImageResponse, @@ -18,7 +19,7 @@ import { createTextResponse, } from "../utils/tool-helpers"; import { - browserContextOutputSchema, + contextOutputSchema, invokeJsFnOutputSchema, invokeJsFnSchema, openTabOutputSchema, @@ -43,7 +44,7 @@ export class BrowserTools { } register(server: McpServer): void { - this.registerGetBasicBrowserContext(server); + this.registerGetContext(server); this.registerCaptureTab(server); this.registerInvokeJsFn(server); this.registerOpenTab(server); @@ -51,22 +52,23 @@ export class BrowserTools { this.registerGetSelection(server); } - private registerGetBasicBrowserContext(server: McpServer): void { - this.logger.verbose("Registering tool: getBasicBrowserContext"); - server.registerTool( - "getBasicBrowserContext", + private registerGetContext(server: McpServer): void { + this.logger.verbose("Registering tool: getContext"); + registerTool( + server, + "getContext", { description: this.toolDescriptionsInputPort.getBasicBrowserContextInstruction(), inputSchema: {}, - outputSchema: browserContextOutputSchema, + outputSchema: contextOutputSchema, }, async () => { - this.logger.verbose("Executing getBasicBrowserContext"); + this.logger.verbose("Executing getContext"); const overCtx = await over(this.toolsInputPort.getContext); if (!overCtx.ok) { - this.logger.error("Failed to get basic browser context", { + this.logger.error("Failed to get context", { reason: overCtx.reason, }); return createErrorResponse( @@ -76,29 +78,18 @@ export class BrowserTools { } const ctx = overCtx.value; - const tabs = ctx.browsers.flatMap((browser) => - browser.browserWindows.flatMap((window) => - window.tabs.map((tab) => ({ - tabKey: tab.tabKey, - windowKey: window.windowKey, - url: tab.url, - title: tab.title, - })), - ), - ); this.logger.verbose("Retrieved browser context", { - tabs, - }); - return createStructuredResponse(browserContextOutputSchema, { - tabs, + browserCount: ctx.browsers.length, }); + return createStructuredResponse(contextOutputSchema, ctx); }, ); } private registerCaptureTab(server: McpServer): void { this.logger.verbose("Registering tool: captureTab"); - server.registerTool( + registerTool( + server, "captureTab", { description: this.toolDescriptionsInputPort.captureTabInstruction(), @@ -139,7 +130,8 @@ export class BrowserTools { private registerInvokeJsFn(server: McpServer): void { this.logger.verbose("Registering tool: invokeJsFn"); - server.registerTool( + registerTool( + server, "invokeJsFn", { description: this.toolDescriptionsInputPort.invokeJsFnInstruction(), @@ -179,7 +171,8 @@ export class BrowserTools { private registerOpenTab(server: McpServer): void { this.logger.verbose("Registering tool: openTab"); - server.registerTool( + registerTool( + server, "openTab", { description: this.toolDescriptionsInputPort.openTabInstruction(), @@ -226,7 +219,8 @@ export class BrowserTools { private registerCloseTab(server: McpServer): void { this.logger.verbose("Registering tool: closeTab"); - server.registerTool( + registerTool( + server, "closeTab", { description: this.toolDescriptionsInputPort.closeTabInstruction(), @@ -261,7 +255,8 @@ export class BrowserTools { private registerGetSelection(server: McpServer): void { this.logger.verbose("Registering tool: getSelection"); - server.registerTool( + registerTool( + server, "getSelection", { description: this.toolDescriptionsInputPort.getSelectionInstruction(), @@ -290,11 +285,9 @@ export class BrowserTools { const selection = overResult.value; this.logger.verbose("Selection retrieved successfully", { tabKey, - hasSelection: !!selection?.selectedText, - }); - return createStructuredResponse(selectionOutputSchema, { - selection: selection?.selectedText ?? null, + hasSelection: !!selection.selectedText, }); + return createStructuredResponse(selectionOutputSchema, selection); }, ); } diff --git a/packages/server-driving-mcp-server/src/services/element-tools.ts b/packages/server-driving-mcp-server/src/services/element-tools.ts index fb8e2d4..09a9e24 100644 --- a/packages/server-driving-mcp-server/src/services/element-tools.ts +++ b/packages/server-driving-mcp-server/src/services/element-tools.ts @@ -11,6 +11,7 @@ import { import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { inject, injectable } from "inversify"; import { over } from "ok-value-error-reason"; +import { registerTool } from "../utils/register-tool"; import { createErrorResponse, createStructuredResponse, @@ -43,7 +44,8 @@ export class ElementTools { private registerGetReadableText(server: McpServer): void { this.logger.verbose("Registering tool: getReadableText"); - server.registerTool( + registerTool( + server, "getReadableText", { description: @@ -73,13 +75,11 @@ export class ElementTools { const innerText = overInnerText.value; this.logger.verbose("Retrieved innerText", { tabKey, - textLength: innerText?.length, + textLength: innerText.length, }); return createStructuredResponse( readableTextOutputSchema, - { - innerText: innerText ?? null, - }, + { innerText }, `InnerText: ${JSON.stringify(innerText)}`, ); }, @@ -88,7 +88,8 @@ export class ElementTools { private registerGetReadableElements(server: McpServer): void { this.logger.verbose("Registering tool: getReadableElements"); - server.registerTool( + registerTool( + server, "getReadableElements", { description: diff --git a/packages/server-driving-mcp-server/src/services/interaction-tools.ts b/packages/server-driving-mcp-server/src/services/interaction-tools.ts index ead00a7..7827920 100644 --- a/packages/server-driving-mcp-server/src/services/interaction-tools.ts +++ b/packages/server-driving-mcp-server/src/services/interaction-tools.ts @@ -11,6 +11,7 @@ import { import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { inject, injectable } from "inversify"; import { over } from "ok-value-error-reason"; +import { registerTool } from "../utils/register-tool"; import { createErrorResponse, createTextResponse } from "../utils/tool-helpers"; import { coordinateSchema, @@ -35,26 +36,27 @@ export class InteractionTools { } register(server: McpServer): void { - this.registerClickOnViewableElement(server); - this.registerFillTextToViewableElement(server); - this.registerHitEnterOnViewableElement(server); + this.registerClickOnCoordinates(server); + this.registerFillTextToCoordinates(server); + this.registerHitEnterOnCoordinates(server); - this.registerClickOnReadableElement(server); - this.registerFillTextToReadableElement(server); - this.registerHitEnterOnReadableElement(server); + this.registerClickOnElement(server); + this.registerFillTextToElement(server); + this.registerHitEnterOnElement(server); } - private registerClickOnViewableElement(server: McpServer): void { - this.logger.verbose("Registering tool: clickOnViewableElement"); - server.registerTool( - "clickOnViewableElement", + private registerClickOnCoordinates(server: McpServer): void { + this.logger.verbose("Registering tool: clickOnCoordinates"); + registerTool( + server, + "clickOnCoordinates", { description: this.toolDescriptionsInputPort.clickOnViewableElementInstruction(), inputSchema: coordinateSchema, }, async ({ tabKey, x, y }) => { - this.logger.info("Executing clickOnViewableElement", { + this.logger.info("Executing clickOnCoordinates", { tabKey, x, y, @@ -64,19 +66,19 @@ export class InteractionTools { ); if (!overClick.ok) { - this.logger.error("Failed to click on viewable element", { + this.logger.error("Failed to click on coordinates", { tabKey, x, y, reason: overClick.reason, }); return createErrorResponse( - "Error clicking on element", + "Error clicking on coordinates", String(overClick.reason), ); } - this.logger.verbose("Clicked on viewable element", { + this.logger.verbose("Clicked on coordinates", { tabKey, x, y, @@ -86,17 +88,18 @@ export class InteractionTools { ); } - private registerFillTextToViewableElement(server: McpServer): void { - this.logger.verbose("Registering tool: fillTextToViewableElement"); - server.registerTool( - "fillTextToViewableElement", + private registerFillTextToCoordinates(server: McpServer): void { + this.logger.verbose("Registering tool: fillTextToCoordinates"); + registerTool( + server, + "fillTextToCoordinates", { description: this.toolDescriptionsInputPort.fillTextToViewableElementInstruction(), inputSchema: coordinateTextInputSchema, }, async ({ tabKey, x, y, value }) => { - this.logger.info("Executing fillTextToViewableElement", { + this.logger.info("Executing fillTextToCoordinates", { tabKey, x, y, @@ -106,7 +109,7 @@ export class InteractionTools { ); if (!overFill.ok) { - this.logger.error("Failed to fill text to viewable element", { + this.logger.error("Failed to fill text to coordinates", { tabKey, x, y, @@ -118,7 +121,7 @@ export class InteractionTools { ); } - this.logger.verbose("Filled text to viewable element", { + this.logger.verbose("Filled text to coordinates", { tabKey, x, y, @@ -129,17 +132,18 @@ export class InteractionTools { ); } - private registerHitEnterOnViewableElement(server: McpServer): void { - this.logger.verbose("Registering tool: hitEnterOnViewableElement"); - server.registerTool( - "hitEnterOnViewableElement", + private registerHitEnterOnCoordinates(server: McpServer): void { + this.logger.verbose("Registering tool: hitEnterOnCoordinates"); + registerTool( + server, + "hitEnterOnCoordinates", { description: this.toolDescriptionsInputPort.hitEnterOnViewableElementInstruction(), inputSchema: coordinateSchema, }, async ({ tabKey, x, y }) => { - this.logger.info("Executing hitEnterOnViewableElement", { + this.logger.info("Executing hitEnterOnCoordinates", { tabKey, x, y, @@ -149,7 +153,7 @@ export class InteractionTools { ); if (!overEnter.ok) { - this.logger.error("Failed to hit enter on viewable element", { + this.logger.error("Failed to hit enter on coordinates", { tabKey, x, y, @@ -161,7 +165,7 @@ export class InteractionTools { ); } - this.logger.verbose("Hit enter on viewable element", { + this.logger.verbose("Hit enter on coordinates", { tabKey, x, y, @@ -171,17 +175,18 @@ export class InteractionTools { ); } - private registerClickOnReadableElement(server: McpServer): void { - this.logger.verbose("Registering tool: clickOnReadableElement"); - server.registerTool( - "clickOnReadableElement", + private registerClickOnElement(server: McpServer): void { + this.logger.verbose("Registering tool: clickOnElement"); + registerTool( + server, + "clickOnElement", { description: this.toolDescriptionsInputPort.clickOnReadableElementInstruction(), inputSchema: readableElementSchema, }, async ({ tabKey, readablePath }) => { - this.logger.info("Executing clickOnReadableElement", { + this.logger.info("Executing clickOnElement", { tabKey, readablePath, }); @@ -190,7 +195,7 @@ export class InteractionTools { ); if (!overClick.ok) { - this.logger.error("Failed to click on readable element", { + this.logger.error("Failed to click on element", { tabKey, readablePath, reason: overClick.reason, @@ -201,7 +206,7 @@ export class InteractionTools { ); } - this.logger.verbose("Clicked on readable element", { + this.logger.verbose("Clicked on element", { tabKey, readablePath, }); @@ -210,17 +215,18 @@ export class InteractionTools { ); } - private registerFillTextToReadableElement(server: McpServer): void { - this.logger.verbose("Registering tool: fillTextToReadableElement"); - server.registerTool( - "fillTextToReadableElement", + private registerFillTextToElement(server: McpServer): void { + this.logger.verbose("Registering tool: fillTextToElement"); + registerTool( + server, + "fillTextToElement", { description: this.toolDescriptionsInputPort.fillTextToReadableElementInstruction(), inputSchema: readableElementTextInputSchema, }, async ({ tabKey, readablePath, value }) => { - this.logger.info("Executing fillTextToReadableElement", { + this.logger.info("Executing fillTextToElement", { tabKey, readablePath, }); @@ -229,7 +235,7 @@ export class InteractionTools { ); if (!overFill.ok) { - this.logger.error("Failed to fill text to readable element", { + this.logger.error("Failed to fill text to element", { tabKey, readablePath, reason: overFill.reason, @@ -240,7 +246,7 @@ export class InteractionTools { ); } - this.logger.verbose("Filled text to readable element", { + this.logger.verbose("Filled text to element", { tabKey, readablePath, valueLength: value.length, @@ -250,17 +256,18 @@ export class InteractionTools { ); } - private registerHitEnterOnReadableElement(server: McpServer): void { - this.logger.verbose("Registering tool: hitEnterOnReadableElement"); - server.registerTool( - "hitEnterOnReadableElement", + private registerHitEnterOnElement(server: McpServer): void { + this.logger.verbose("Registering tool: hitEnterOnElement"); + registerTool( + server, + "hitEnterOnElement", { description: this.toolDescriptionsInputPort.hitEnterOnReadableElementInstruction(), inputSchema: readableElementSchema, }, async ({ tabKey, readablePath }) => { - this.logger.info("Executing hitEnterOnReadableElement", { + this.logger.info("Executing hitEnterOnElement", { tabKey, readablePath, }); @@ -269,7 +276,7 @@ export class InteractionTools { ); if (!overEnter.ok) { - this.logger.error("Failed to hit enter on readable element", { + this.logger.error("Failed to hit enter on element", { tabKey, readablePath, reason: overEnter.reason, @@ -280,7 +287,7 @@ export class InteractionTools { ); } - this.logger.verbose("Hit enter on readable element", { + this.logger.verbose("Hit enter on element", { tabKey, readablePath, }); diff --git a/packages/server-driving-mcp-server/src/utils/register-tool.ts b/packages/server-driving-mcp-server/src/utils/register-tool.ts new file mode 100644 index 0000000..e729be6 --- /dev/null +++ b/packages/server-driving-mcp-server/src/utils/register-tool.ts @@ -0,0 +1,30 @@ +import type { ServerToolName } from "@mcp-browser-kit/core-server/input-ports"; +import type { McpServer, ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { z } from "zod"; + +type ZodObjectShape = Record; + +interface ToolConfig< + TInput extends ZodObjectShape, + TOutput extends ZodObjectShape | undefined, +> { + description: string; + inputSchema: TInput; + outputSchema?: TOutput; +} + +/** + * Type-safe wrapper for server.registerTool that enforces the tool name + * to be a valid ServerToolName while preserving schema-to-handler type inference + */ +export const registerTool = < + TInput extends ZodObjectShape, + TOutput extends ZodObjectShape | undefined = undefined, +>( + server: McpServer, + name: ServerToolName, + config: ToolConfig, + handler: ToolCallback, +): void => { + server.registerTool(name, config, handler); +}; diff --git a/packages/server-driving-mcp-server/src/utils/tool-schemas.ts b/packages/server-driving-mcp-server/src/utils/tool-schemas.ts index ab27483..7593de7 100644 --- a/packages/server-driving-mcp-server/src/utils/tool-schemas.ts +++ b/packages/server-driving-mcp-server/src/utils/tool-schemas.ts @@ -37,15 +37,34 @@ export const openTabSchema = { url: z.string().describe("URL to open in the new tab"), }; -export const tabInfoOutputSchema = { +export const browserTabContextSchema = { tabKey: z.string().describe("Unique key identifying the tab"), - windowKey: z.string().describe("Window key where the tab is located"), - url: z.string().describe("Current URL of the tab"), + active: z.boolean().describe("Whether the tab is currently active"), title: z.string().describe("Title of the tab"), + url: z.string().describe("Current URL of the tab"), +}; + +export const browserWindowContextSchema = { + windowKey: z.string().describe("Unique key identifying the window"), + tabs: z + .array(z.object(browserTabContextSchema)) + .describe("List of tabs in this window"), +}; + +export const browserContextSchema = { + browserId: z.string().describe("Unique identifier for the browser"), + availableTools: z + .array(z.string()) + .describe("List of available tool names for this browser"), + browserWindows: z + .array(z.object(browserWindowContextSchema)) + .describe("List of browser windows"), }; -export const browserContextOutputSchema = { - tabs: z.array(z.object(tabInfoOutputSchema)).describe("List of open tabs"), +export const contextOutputSchema = { + browsers: z + .array(z.object(browserContextSchema)) + .describe("List of connected browsers"), }; export const openTabOutputSchema = { @@ -66,11 +85,11 @@ export const readableElementOutputSchema = { }; export const selectionOutputSchema = { - selection: z.string().nullable().describe("Selected text on the page, or null if none"), + selectedText: z.string().describe("Selected text on the page"), }; export const readableTextOutputSchema = { - innerText: z.string().nullable().describe("Inner text content of the page"), + innerText: z.string().describe("Inner text content of the page"), }; export const invokeJsFnOutputSchema = { diff --git a/yarn.lock b/yarn.lock index 74b729b..550397c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -263,366 +263,212 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/aix-ppc64@npm:0.25.10" - conditions: os=aix & cpu=ppc64 +"@emnapi/core@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/core@npm:1.7.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.1.0" + tslib: "npm:^2.4.0" + checksum: 10c0/f3740be23440b439333e3ae3832163f60c96c4e35337f3220ceba88f36ee89a57a871d27c94eb7a9ff98a09911ed9a2089e477ab549f4d30029f8b907f84a351 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/aix-ppc64@npm:0.27.0" - conditions: os=aix & cpu=ppc64 +"@emnapi/runtime@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/runtime@npm:1.7.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/android-arm64@npm:0.25.10" - conditions: os=android & cpu=arm64 +"@emnapi/wasi-threads@npm:1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/e6d54bf2b1e64cdd83d2916411e44e579b6ae35d5def0dea61a3c452d9921373044dff32a8b8473ae60c80692bdc39323e98b96a3f3d87ba6886b24dd0ef7ca1 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/android-arm64@npm:0.27.0" - conditions: os=android & cpu=arm64 +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/android-arm@npm:0.25.10" - conditions: os=android & cpu=arm +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/android-arm@npm:0.27.0" +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/android-x64@npm:0.25.10" +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/android-x64@npm:0.27.0" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/darwin-arm64@npm:0.25.10" +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/darwin-arm64@npm:0.27.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/darwin-x64@npm:0.25.10" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/darwin-x64@npm:0.27.0" +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/freebsd-arm64@npm:0.25.10" +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/freebsd-arm64@npm:0.27.0" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/freebsd-x64@npm:0.25.10" +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/freebsd-x64@npm:0.27.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-arm64@npm:0.25.10" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-arm64@npm:0.27.0" +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-arm@npm:0.25.10" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-arm@npm:0.27.0" +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-ia32@npm:0.25.10" +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-ia32@npm:0.27.0" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-loong64@npm:0.25.10" +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-loong64@npm:0.27.0" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-mips64el@npm:0.25.10" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-mips64el@npm:0.27.0" +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-ppc64@npm:0.25.10" +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-ppc64@npm:0.27.0" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-riscv64@npm:0.25.10" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-riscv64@npm:0.27.0" +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-s390x@npm:0.25.10" +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-s390x@npm:0.27.0" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/linux-x64@npm:0.25.10" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/linux-x64@npm:0.27.0" +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/netbsd-arm64@npm:0.25.10" +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/netbsd-arm64@npm:0.27.0" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/netbsd-x64@npm:0.25.10" +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/netbsd-x64@npm:0.27.0" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/openbsd-arm64@npm:0.25.10" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/openbsd-arm64@npm:0.27.0" +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/openbsd-x64@npm:0.25.10" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/openbsd-x64@npm:0.27.0" +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/openharmony-arm64@npm:0.25.10" +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/openharmony-arm64@npm:0.27.0" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/sunos-x64@npm:0.25.10" +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/sunos-x64@npm:0.27.0" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/win32-arm64@npm:0.25.10" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/win32-arm64@npm:0.27.0" +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/win32-ia32@npm:0.25.10" +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/win32-ia32@npm:0.27.0" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.25.10": - version: 0.25.10 - resolution: "@esbuild/win32-x64@npm:0.25.10" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.27.0": - version: 0.27.0 - resolution: "@esbuild/win32-x64@npm:0.27.0" +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1051,6 +897,17 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^1.1.0": + version: 1.1.1 + resolution: "@napi-rs/wasm-runtime@npm:1.1.1" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10c0/04d57b67e80736e41fe44674a011878db0a8ad893f4d44abb9d3608debb7c174224cba2796ed5b0c1d367368159f3ca6be45f1c59222f70e32ddc880f803d447 + languageName: node + linkType: hard + "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -1073,6 +930,148 @@ __metadata: languageName: node linkType: hard +"@oxc-resolver/binding-android-arm-eabi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.16.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm64@npm:11.16.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.16.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-x64@npm:11.16.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-freebsd-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.16.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.16.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-openharmony-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-openharmony-arm64@npm:11.16.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-wasm32-wasi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.16.2" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@pinojs/redact@npm:^0.4.0": version: 0.4.0 resolution: "@pinojs/redact@npm:0.4.0" @@ -1296,6 +1295,176 @@ __metadata: languageName: node linkType: hard +"@swc-node/core@npm:^1.14.1": + version: 1.14.1 + resolution: "@swc-node/core@npm:1.14.1" + peerDependencies: + "@swc/core": ">= 1.13.3" + "@swc/types": ">= 0.1" + checksum: 10c0/073a0a1d782eafcfc3d2056ad9c5232ec4a0a0a098abafa3eafdde30832eb04a2430cec943fef3bbf9754eb37b0bf6e749f9303304ac42e318936ced35f6144b + languageName: node + linkType: hard + +"@swc-node/register@npm:^1.11.1": + version: 1.11.1 + resolution: "@swc-node/register@npm:1.11.1" + dependencies: + "@swc-node/core": "npm:^1.14.1" + "@swc-node/sourcemap-support": "npm:^0.6.1" + colorette: "npm:^2.0.20" + debug: "npm:^4.4.1" + oxc-resolver: "npm:^11.6.1" + pirates: "npm:^4.0.7" + tslib: "npm:^2.8.1" + peerDependencies: + "@swc/core": ">= 1.4.13" + typescript: ">= 4.3" + checksum: 10c0/ac4c4f7a6cbf96a83c5f1edb346d0db3290f39be4c56e9a255b1cba672303074a53cdd06956b6b9ada96c386def6f9cb59d0f274ce81fbc04f7178e2974ec7f0 + languageName: node + linkType: hard + +"@swc-node/sourcemap-support@npm:^0.6.1": + version: 0.6.1 + resolution: "@swc-node/sourcemap-support@npm:0.6.1" + dependencies: + source-map-support: "npm:^0.5.21" + tslib: "npm:^2.8.1" + checksum: 10c0/6c4bf90815adf9e3d95c7ee5d3b7ea98aa1e3bf28c24d2c3c960d18271d4122edd2906699942802503d3c07d69e0a8c8e8618c7cfc6212d646bde25503e858c4 + languageName: node + linkType: hard + +"@swc/core-darwin-arm64@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-darwin-arm64@npm:1.15.8" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-darwin-x64@npm:1.15.8" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.8" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.8" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.8" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.8" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-x64-musl@npm:1.15.8" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.8" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.8" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.8" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.15.8": + version: 1.15.8 + resolution: "@swc/core@npm:1.15.8" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.8" + "@swc/core-darwin-x64": "npm:1.15.8" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.8" + "@swc/core-linux-arm64-gnu": "npm:1.15.8" + "@swc/core-linux-arm64-musl": "npm:1.15.8" + "@swc/core-linux-x64-gnu": "npm:1.15.8" + "@swc/core-linux-x64-musl": "npm:1.15.8" + "@swc/core-win32-arm64-msvc": "npm:1.15.8" + "@swc/core-win32-ia32-msvc": "npm:1.15.8" + "@swc/core-win32-x64-msvc": "npm:1.15.8" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.25" + peerDependencies: + "@swc/helpers": ">=0.5.17" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10c0/929f334a224776fdb3c4a8aaba68f07666ff56fae7502a9459bc9666cb73d94e65f042ce8c4ef4e6746a8bb3f8255cbe8599bef6e3181269caf761c8e55513cf + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.3": + version: 0.1.3 + resolution: "@swc/counter@npm:0.1.3" + checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10c0/847a5b20b131281f89d640a7ed4887fb65724807d53d334b230e84b98c21097aa10cd28a074f9ed287a6ce109e443dd4bafbe7dcfb62333d7806c4ea3e7f8aca + languageName: node + linkType: hard + "@trpc/client@npm:^11.7.2": version: 11.7.2 resolution: "@trpc/client@npm:11.7.2" @@ -1315,6 +1484,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + languageName: node + linkType: hard + "@types/data-urls@npm:^3.0.4": version: 3.0.4 resolution: "@types/data-urls@npm:3.0.4" @@ -2102,7 +2280,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.7": +"colorette@npm:^2.0.20, colorette@npm:^2.0.7": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 @@ -2673,125 +2851,36 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.27.0": - version: 0.27.0 - resolution: "esbuild@npm:0.27.0" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.0" - "@esbuild/android-arm": "npm:0.27.0" - "@esbuild/android-arm64": "npm:0.27.0" - "@esbuild/android-x64": "npm:0.27.0" - "@esbuild/darwin-arm64": "npm:0.27.0" - "@esbuild/darwin-x64": "npm:0.27.0" - "@esbuild/freebsd-arm64": "npm:0.27.0" - "@esbuild/freebsd-x64": "npm:0.27.0" - "@esbuild/linux-arm": "npm:0.27.0" - "@esbuild/linux-arm64": "npm:0.27.0" - "@esbuild/linux-ia32": "npm:0.27.0" - "@esbuild/linux-loong64": "npm:0.27.0" - "@esbuild/linux-mips64el": "npm:0.27.0" - "@esbuild/linux-ppc64": "npm:0.27.0" - "@esbuild/linux-riscv64": "npm:0.27.0" - "@esbuild/linux-s390x": "npm:0.27.0" - "@esbuild/linux-x64": "npm:0.27.0" - "@esbuild/netbsd-arm64": "npm:0.27.0" - "@esbuild/netbsd-x64": "npm:0.27.0" - "@esbuild/openbsd-arm64": "npm:0.27.0" - "@esbuild/openbsd-x64": "npm:0.27.0" - "@esbuild/openharmony-arm64": "npm:0.27.0" - "@esbuild/sunos-x64": "npm:0.27.0" - "@esbuild/win32-arm64": "npm:0.27.0" - "@esbuild/win32-ia32": "npm:0.27.0" - "@esbuild/win32-x64": "npm:0.27.0" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/a3a1deec285337b7dfe25cbb9aa8765d27a0192b610a8477a39bf5bd907a6bdb75e98898b61fb4337114cfadb13163bd95977db14e241373115f548e235b40a2 - languageName: node - linkType: hard - -"esbuild@npm:~0.25.0": - version: 0.25.10 - resolution: "esbuild@npm:0.25.10" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.10" - "@esbuild/android-arm": "npm:0.25.10" - "@esbuild/android-arm64": "npm:0.25.10" - "@esbuild/android-x64": "npm:0.25.10" - "@esbuild/darwin-arm64": "npm:0.25.10" - "@esbuild/darwin-x64": "npm:0.25.10" - "@esbuild/freebsd-arm64": "npm:0.25.10" - "@esbuild/freebsd-x64": "npm:0.25.10" - "@esbuild/linux-arm": "npm:0.25.10" - "@esbuild/linux-arm64": "npm:0.25.10" - "@esbuild/linux-ia32": "npm:0.25.10" - "@esbuild/linux-loong64": "npm:0.25.10" - "@esbuild/linux-mips64el": "npm:0.25.10" - "@esbuild/linux-ppc64": "npm:0.25.10" - "@esbuild/linux-riscv64": "npm:0.25.10" - "@esbuild/linux-s390x": "npm:0.25.10" - "@esbuild/linux-x64": "npm:0.25.10" - "@esbuild/netbsd-arm64": "npm:0.25.10" - "@esbuild/netbsd-x64": "npm:0.25.10" - "@esbuild/openbsd-arm64": "npm:0.25.10" - "@esbuild/openbsd-x64": "npm:0.25.10" - "@esbuild/openharmony-arm64": "npm:0.25.10" - "@esbuild/sunos-x64": "npm:0.25.10" - "@esbuild/win32-arm64": "npm:0.25.10" - "@esbuild/win32-ia32": "npm:0.25.10" - "@esbuild/win32-x64": "npm:0.25.10" +"esbuild@npm:^0.27.0, esbuild@npm:~0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -2847,7 +2936,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/8ee5fdd43ed0d4092ce7f41577c63147f54049d5617763f0549c638bbe939e8adaa8f1a2728adb63417eb11df51956b7b0d8eb88ee08c27ad1d42960256158fa + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd languageName: node linkType: hard @@ -4640,6 +4729,75 @@ __metadata: languageName: node linkType: hard +"oxc-resolver@npm:^11.6.1": + version: 11.16.2 + resolution: "oxc-resolver@npm:11.16.2" + dependencies: + "@oxc-resolver/binding-android-arm-eabi": "npm:11.16.2" + "@oxc-resolver/binding-android-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-x64": "npm:11.16.2" + "@oxc-resolver/binding-freebsd-x64": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-musleabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-ppc64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-musl": "npm:11.16.2" + "@oxc-resolver/binding-openharmony-arm64": "npm:11.16.2" + "@oxc-resolver/binding-wasm32-wasi": "npm:11.16.2" + "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-ia32-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-x64-msvc": "npm:11.16.2" + dependenciesMeta: + "@oxc-resolver/binding-android-arm-eabi": + optional: true + "@oxc-resolver/binding-android-arm64": + optional: true + "@oxc-resolver/binding-darwin-arm64": + optional: true + "@oxc-resolver/binding-darwin-x64": + optional: true + "@oxc-resolver/binding-freebsd-x64": + optional: true + "@oxc-resolver/binding-linux-arm-gnueabihf": + optional: true + "@oxc-resolver/binding-linux-arm-musleabihf": + optional: true + "@oxc-resolver/binding-linux-arm64-gnu": + optional: true + "@oxc-resolver/binding-linux-arm64-musl": + optional: true + "@oxc-resolver/binding-linux-ppc64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-musl": + optional: true + "@oxc-resolver/binding-linux-s390x-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-musl": + optional: true + "@oxc-resolver/binding-openharmony-arm64": + optional: true + "@oxc-resolver/binding-wasm32-wasi": + optional: true + "@oxc-resolver/binding-win32-arm64-msvc": + optional: true + "@oxc-resolver/binding-win32-ia32-msvc": + optional: true + "@oxc-resolver/binding-win32-x64-msvc": + optional: true + checksum: 10c0/b20a0fea18fdf31dbaee51354ce7b987ba8f3e780c6c1de9034628033a69d0b3085f9596d9925797d9340bdf4b98cd72a258b0728d0d5e5de2b1748154921b42 + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -4888,7 +5046,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1": +"pirates@npm:^4.0.1, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a @@ -5335,6 +5493,8 @@ __metadata: "@mozilla/readability": "npm:^0.6.0" "@playwright/test": "npm:^1.57.0" "@readme/data-urls": "npm:^3.0.0" + "@swc-node/register": "npm:^1.11.1" + "@swc/core": "npm:^1.15.8" "@trpc/client": "npm:^11.7.2" "@trpc/server": "npm:^11.7.2" "@types/data-urls": "npm:^3.0.4" @@ -5647,7 +5807,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.21": +"source-map-support@npm:0.5.21, source-map-support@npm:^0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -6058,7 +6218,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.8.1": +"tslib@npm:2.8.1, tslib@npm:^2.4.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -6108,10 +6268,10 @@ __metadata: linkType: hard "tsx@npm:^4.20.6": - version: 4.20.6 - resolution: "tsx@npm:4.20.6" + version: 4.21.0 + resolution: "tsx@npm:4.21.0" dependencies: - esbuild: "npm:~0.25.0" + esbuild: "npm:~0.27.0" fsevents: "npm:~2.3.3" get-tsconfig: "npm:^4.7.5" dependenciesMeta: @@ -6119,7 +6279,7 @@ __metadata: optional: true bin: tsx: dist/cli.mjs - checksum: 10c0/07757a9bf62c271e0a00869b2008c5f2d6e648766536e4faf27d9d8027b7cde1ac8e4871f4bb570c99388bcee0018e6869dad98c07df809b8052f9c549cd216f + checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b languageName: node linkType: hard