From 0c55d923d1623bc8dfd205f2308f0a2377efebd3 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 7 Apr 2026 23:07:09 -0700 Subject: [PATCH 1/5] Add MCP server for agent-accessible devtools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces `@player-devtools/mcp` — a Model Context Protocol server that exposes Player runtime state (players, flows, data, logs, plugin data) as MCP tools queryable by AI agents (e.g. Claude). - Extract `useStateReducer` from `plugin/core` → `@player-devtools/utils` - Extract `createExtensionClient` pure factory from `useExtensionState` - Add `FlipperServerTransport` with auto-start headless `flipper-server` - Register 9 MCP tools: list_players, get_player_status, get_flow, get_data, get_logs, get_plugin_data, describe_plugin, select_player, invoke_action Co-Authored-By: Claude Sonnet 4.6 --- devtools/client/src/index.ts | 1 + devtools/client/src/state/client.ts | 90 ++ devtools/client/src/state/index.ts | 129 +- devtools/mcp/BUILD | 53 + devtools/mcp/bin/run | 19 + devtools/mcp/package.json | 17 + devtools/mcp/src/index.ts | 7 + devtools/mcp/src/server.ts | 118 ++ devtools/mcp/src/tools/flow.ts | 174 ++ devtools/mcp/src/tools/index.ts | 4 + devtools/mcp/src/tools/players.ts | 72 + devtools/mcp/src/tools/plugins.ts | 52 + devtools/mcp/src/tools/select.ts | 88 ++ devtools/mcp/src/transport.ts | 347 ++++ devtools/plugin/core/src/plugin.ts | 6 +- devtools/plugin/core/src/state.ts | 43 - devtools/utils/core/src/index.ts | 41 + justfile | 9 + package.json | 9 +- pnpm-lock.yaml | 2278 ++++++++++++++++++++++++++- pnpm-workspace.yaml | 1 + 21 files changed, 3387 insertions(+), 171 deletions(-) create mode 100644 devtools/client/src/state/client.ts create mode 100644 devtools/mcp/BUILD create mode 100644 devtools/mcp/bin/run create mode 100644 devtools/mcp/package.json create mode 100644 devtools/mcp/src/index.ts create mode 100644 devtools/mcp/src/server.ts create mode 100644 devtools/mcp/src/tools/flow.ts create mode 100644 devtools/mcp/src/tools/index.ts create mode 100644 devtools/mcp/src/tools/players.ts create mode 100644 devtools/mcp/src/tools/plugins.ts create mode 100644 devtools/mcp/src/tools/select.ts create mode 100644 devtools/mcp/src/transport.ts delete mode 100644 devtools/plugin/core/src/state.ts diff --git a/devtools/client/src/index.ts b/devtools/client/src/index.ts index 8628fc5..531c6a1 100644 --- a/devtools/client/src/index.ts +++ b/devtools/client/src/index.ts @@ -1 +1,2 @@ export { Panel } from "./panel"; +export { createExtensionClient, type ExtensionClient } from "./state/client"; diff --git a/devtools/client/src/state/client.ts b/devtools/client/src/state/client.ts new file mode 100644 index 0000000..2637b54 --- /dev/null +++ b/devtools/client/src/state/client.ts @@ -0,0 +1,90 @@ +import { Messenger } from "@player-devtools/messenger"; +import type { + CommunicationLayerMethods, + ExtensionState, + ExtensionSupportedEvents, +} from "@player-devtools/types"; +import { useStateReducer } from "@player-devtools/utils"; + +import { INITIAL_EXTENSION_STATE } from "../constants"; +import { reducer } from "./reducer"; + +const NOOP_ID = -1; + +export type ExtensionClient = { + getState: () => ExtensionState; + subscribe: (fn: (state: ExtensionState) => void) => () => void; + selectPlayer: (playerID: string) => void; + selectPlugin: (pluginID: string) => void; + handleInteraction: (interaction: { type: string; payload?: string }) => void; + destroy: () => void; +}; + +export const createExtensionClient = ( + communicationLayer: CommunicationLayerMethods, +): ExtensionClient => { + const store = useStateReducer(reducer, INITIAL_EXTENSION_STATE); + + const messenger = new Messenger({ + context: "devtools", + messageCallback: (message) => store.dispatch(message), + ...communicationLayer, + logger: console, + }); + + const selectPlayer = (playerID: string): void => { + store.dispatch({ + id: NOOP_ID, + sender: "internal", + context: "devtools", + _messenger_: false, + timestamp: Date.now(), + type: "PLAYER_DEVTOOLS_PLAYER_SELECTED", + payload: { playerID }, + }); + + messenger.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { + type: "player-selected", + payload: playerID, + }, + }); + }; + + const selectPlugin = (pluginID: string): void => { + store.dispatch({ + id: NOOP_ID, + sender: "internal", + context: "devtools", + _messenger_: false, + timestamp: Date.now(), + type: "PLAYER_DEVTOOLS_PLUGIN_SELECTED", + payload: { pluginID }, + }); + }; + + const handleInteraction = ({ + type, + payload, + }: { + type: string; + payload?: string; + }): void => { + const { current } = store.getState(); + messenger.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type, payload }, + ...(current.player ? { target: current.player } : {}), + }); + }; + + return { + getState: store.getState, + subscribe: store.subscribe, + selectPlayer, + selectPlugin, + handleInteraction, + destroy: () => messenger.destroy(), + }; +}; diff --git a/devtools/client/src/state/index.ts b/devtools/client/src/state/index.ts index 834563e..b2bcb00 100644 --- a/devtools/client/src/state/index.ts +++ b/devtools/client/src/state/index.ts @@ -1,126 +1,33 @@ -import { Messenger } from "@player-devtools/messenger"; -import type { - ExtensionSupportedEvents, - MessengerOptions, -} from "@player-devtools/types"; -import { useCallback, useEffect, useMemo, useReducer } from "react"; +import type { CommunicationLayerMethods } from "@player-devtools/types"; +import { useEffect, useMemo, useSyncExternalStore } from "react"; -import { INITIAL_EXTENSION_STATE } from "../constants"; -import { reducer } from "./reducer"; - -const NOOP_ID = -1; +import { createExtensionClient } from "./client"; /** - * Custom React hook for managing the state of the devtools extension. - * - * This hook initializes the extension's state and sets up a communication layer - * using the `Messenger` class. It provides methods to select a player or plugin, - * and handle interactions, which dispatch actions to update the state accordingly. + * Thin React adapter over `createExtensionClient`. * + * Creates the client once per `communicationLayer` identity, subscribes to + * state via `useSyncExternalStore`, and tears down the Messenger on unmount. */ export const useExtensionState = ({ communicationLayer, }: { /** the communication layer to use for the extension */ - communicationLayer: Pick< - MessengerOptions, - "sendMessage" | "addListener" | "removeListener" - >; + communicationLayer: CommunicationLayerMethods; }) => { - const [state, dispatch] = useReducer(reducer, INITIAL_EXTENSION_STATE); - - const messengerOptions = useMemo>( - () => ({ - context: "devtools", - target: "player", - messageCallback: (message) => { - dispatch(message); - }, - ...communicationLayer, - logger: console, - }), - [dispatch, communicationLayer], + const client = useMemo( + () => createExtensionClient(communicationLayer), + [communicationLayer], ); - const messenger = useMemo( - () => new Messenger(messengerOptions), - [messengerOptions], - ); - - useEffect(() => { - return () => { - messenger.destroy(); - }; - }, []); - - const selectPlayer = useCallback( - (playerID: string) => { - dispatch({ - id: NOOP_ID, - sender: "internal", - context: "devtools", - _messenger_: false, - timestamp: Date.now(), - type: "PLAYER_DEVTOOLS_PLAYER_SELECTED", - payload: { - playerID, - }, - }); + useEffect(() => () => client.destroy(), [client]); - messenger.sendMessage({ - type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", - payload: { - type: "player-selected", - payload: playerID, - }, - }); - }, - [dispatch], - ); - - const selectPlugin = useCallback( - (pluginID: string) => { - dispatch({ - id: NOOP_ID, - sender: "internal", - context: "devtools", - _messenger_: false, - timestamp: Date.now(), - type: "PLAYER_DEVTOOLS_PLUGIN_SELECTED", - payload: { - pluginID, - }, - }); - }, - [dispatch], - ); - - /** - * Plugin authors can add interactive elements to the Player-UI content by leveraging - * the pub-sub plugin and having the handle interaction proxy the message to the inspected - * Player-UI instance. - */ - const handleInteraction = useCallback( - ({ - type, - payload, - }: { - /** interaction type */ - type: string; - /** interaction payload */ - payload?: string; - }) => { - messenger.sendMessage({ - type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", - payload: { - type, - payload, - }, - ...(state.current.player ? { target: state.current.player } : {}), - }); - }, - [messenger], - ); + const state = useSyncExternalStore(client.subscribe, client.getState); - return { state, selectPlayer, selectPlugin, handleInteraction }; + return { + state, + selectPlayer: client.selectPlayer, + selectPlugin: client.selectPlugin, + handleInteraction: client.handleInteraction, + }; }; diff --git a/devtools/mcp/BUILD b/devtools/mcp/BUILD new file mode 100644 index 0000000..b72c000 --- /dev/null +++ b/devtools/mcp/BUILD @@ -0,0 +1,53 @@ +load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path") +load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary") +load("@npm//:defs.bzl", "npm_link_all_packages") +load("@rules_player//javascript:defs.bzl", "js_pipeline") +load("//helpers:defs.bzl", "tsup_config", "vitest_config") + +npm_link_all_packages(name = "node_modules") + +tsup_config(name = "tsup_config") + +vitest_config(name = "vitest_config") + +deps = [ + ":node_modules/@player-devtools/client", + ":node_modules/@player-devtools/messenger", + ":node_modules/@player-devtools/types", + "//:node_modules/@modelcontextprotocol/sdk", + "//:node_modules/@types/ws", + "//:node_modules/flipper-server", + "//:node_modules/flipper-server-client", + "//:node_modules/ws", + "//:node_modules/zod", +] + +js_pipeline( + package_name = "@player-devtools/mcp", + deps = deps, + srcs = glob(["src/**/*", "bin/**/*"]) +) + +directory_path( + name = "entrypoint", + directory = ":mcp", + path = "bin/run", +) + +js_binary( + name = "start", + entry_point = ":entrypoint", + data = [":mcp"] + deps, +) + +# directory_path( +# name = "mcp_inspector_entry", +# directory = "//:node_modules/@modelcontextprotocol/inspector/dir", +# path = "cli/build/cli.js", +# ) + +# js_binary( +# name = "mcp_inspector", +# data = ["//:node_modules", ":mcp_server"], +# entry_point = ":mcp_inspector_entry", +# ) diff --git a/devtools/mcp/bin/run b/devtools/mcp/bin/run new file mode 100644 index 0000000..cf74dd5 --- /dev/null +++ b/devtools/mcp/bin/run @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +const { FlipperServerTransport, MCPServer } = require("@player-devtools/mcp"); + +const transport = new FlipperServerTransport(); +const server = new MCPServer(transport); + +server.start().catch((err) => { + console.error("Failed to start MCP server:", err); + process.exit(1); +}); + +process.on("SIGINT", () => { + server.stop().then(() => process.exit(0)); +}); + +process.on("SIGTERM", () => { + server.stop().then(() => process.exit(0)); +}); diff --git a/devtools/mcp/package.json b/devtools/mcp/package.json new file mode 100644 index 0000000..7aa4f43 --- /dev/null +++ b/devtools/mcp/package.json @@ -0,0 +1,17 @@ +{ + "name": "@player-devtools/mcp", + "version": "0.0.0-PLACEHOLDER", + "main": "src/index.ts", + "bin": { + "player-devtools-mcp": "bin/run" + }, + "files": [ + "dist", + "bin" + ], + "dependencies": { + "@player-devtools/client": "workspace:*", + "@player-devtools/messenger": "workspace:*", + "@player-devtools/types": "workspace:*" + } +} diff --git a/devtools/mcp/src/index.ts b/devtools/mcp/src/index.ts new file mode 100644 index 0000000..3edeadb --- /dev/null +++ b/devtools/mcp/src/index.ts @@ -0,0 +1,7 @@ +export { MCPServer } from "./server"; +export { + type Transport, + FlipperServerTransport, + WebSocketServerTransport, + DEFAULT_WS_PORT, +} from "./transport"; diff --git a/devtools/mcp/src/server.ts b/devtools/mcp/src/server.ts new file mode 100644 index 0000000..3433b47 --- /dev/null +++ b/devtools/mcp/src/server.ts @@ -0,0 +1,118 @@ +import { + createExtensionClient, + type ExtensionClient, +} from "@player-devtools/client"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; + +import type { Transport } from "./transport"; +import { + listPlayersTool, + getPlayerStatusTool, + getFlowTool, + getDataTool, + getLogsTool, + getPluginDataTool, + describePluginTool, + selectPlayerTool, + invokeActionTool, + handleListPlayers, + handleGetPlayerStatus, + handleGetFlow, + handleGetData, + handleGetLogs, + handleGetPluginData, + handleDescribePlugin, + handleSelectPlayer, + handleInvokeAction, +} from "./tools"; + +const ALL_TOOLS = [ + listPlayersTool, + getPlayerStatusTool, + getFlowTool, + getDataTool, + getLogsTool, + getPluginDataTool, + describePluginTool, + selectPlayerTool, + invokeActionTool, +]; + +export class MCPServer { + private client: ExtensionClient; + private server: Server; + + constructor(private transport: Transport) { + this.client = createExtensionClient(transport); + this.server = new Server( + { name: "player-devtools", version: "0.0.1" }, + { capabilities: { tools: {} } }, + ); + this.registerHandlers(); + } + + private registerHandlers(): void { + this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: ALL_TOOLS, + })); + + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + const c = this.client; + + switch (name) { + case "list_players": + return handleListPlayers(c); + case "get_player_status": + return handleGetPlayerStatus(c, args); + case "get_flow": + return handleGetFlow(c, args); + case "get_data": + return handleGetData(c, args); + case "get_logs": + return handleGetLogs(c, args); + case "get_plugin_data": + return handleGetPluginData(c, args); + case "describe_plugin": + return handleDescribePlugin(c, args); + case "select_player": + return handleSelectPlayer(c, args); + case "invoke_action": + return handleInvokeAction(c, args); + default: + return { + content: [ + { + type: "text" as const, + text: JSON.stringify({ error: `unknown tool: ${name}` }), + }, + ], + }; + } + }); + } + + async start(): Promise { + const stdioTransport = new StdioServerTransport(); + await this.server.connect(stdioTransport); + try { + await this.transport.connect(); + } catch (err) { + console.warn( + "[MCPServer] Transport connect failed (will operate in disconnected mode):", + err instanceof Error ? err.message : err, + ); + } + } + + async stop(): Promise { + this.client.destroy(); + await this.transport.close(); + await this.server.close(); + } +} diff --git a/devtools/mcp/src/tools/flow.ts b/devtools/mcp/src/tools/flow.ts new file mode 100644 index 0000000..9c4f246 --- /dev/null +++ b/devtools/mcp/src/tools/flow.ts @@ -0,0 +1,174 @@ +import type { ExtensionClient } from "@player-devtools/client"; +import type { PluginData } from "@player-devtools/types"; +import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; + +type Failure = { error: string }; +type PlayerOk = { + id: string; + player: { + plugins: Record; + active: boolean; + config: Record; + }; +}; +type BasicPluginOk = PlayerOk & { basicPlugin: PluginData }; + +const PlayerInput = z.object({ playerId: z.string().optional() }); + +export const getFlowTool: Tool = { + name: "get_flow", + description: + "Get the current flow from the basic devtools plugin for a Player instance.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + }, + required: [], + }, +}; + +export const getDataTool: Tool = { + name: "get_data", + description: + "Get the current flow data model from the basic devtools plugin for a Player instance.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + }, + required: [], + }, +}; + +export const getLogsTool: Tool = { + name: "get_logs", + description: + "Get the logs from the basic devtools plugin for a Player instance.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + }, + required: [], + }, +}; + +export const getPluginDataTool: Tool = { + name: "get_plugin_data", + description: "Get a specific data key from any plugin for a Player instance.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + pluginId: { type: "string", description: "Plugin ID." }, + dataKey: { + type: "string", + description: "Key to retrieve from the plugin's data.", + }, + }, + required: ["pluginId", "dataKey"], + }, +}; + +function resolvePlayer( + client: ExtensionClient, + playerId?: string, +): Failure | PlayerOk { + const { players, current } = client.getState(); + const id = playerId ?? current.player; + if (!id) return { error: "no player selected" }; + const player = players[id]; + if (!player) return { error: `player not found: ${id}` }; + return { id, player }; +} + +function resolveBasicPlugin( + client: ExtensionClient, + playerId?: string, +): Failure | BasicPluginOk { + const resolved = resolvePlayer(client, playerId); + if ("error" in resolved) return resolved; + const { id, player } = resolved; + // Find the first plugin — by convention the basic plugin is registered first + const [basicPlugin] = Object.values(player.plugins); + if (!basicPlugin) return { error: "no plugin registered for this player" }; + return { id, player, basicPlugin }; +} + +function ok(value: unknown): CallToolResult { + return { content: [{ type: "text", text: JSON.stringify(value) }] }; +} + +function err(message: string): CallToolResult { + return { + content: [{ type: "text", text: JSON.stringify({ error: message }) }], + }; +} + +export function handleGetFlow( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId } = PlayerInput.parse(input); + const resolved = resolveBasicPlugin(client, playerId); + if ("error" in resolved) return err(resolved.error); + return ok(resolved.basicPlugin.flow); +} + +export function handleGetData( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId } = PlayerInput.parse(input); + const resolved = resolveBasicPlugin(client, playerId); + if ("error" in resolved) return err(resolved.error); + return ok(resolved.basicPlugin.flow?.data ?? null); +} + +export function handleGetLogs( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId } = PlayerInput.parse(input); + const resolved = resolveBasicPlugin(client, playerId); + if ("error" in resolved) return err(resolved.error); + // Logs live in the plugin's flow data under a "logs" key by convention + return ok( + (resolved.basicPlugin.flow?.data as Record)?.logs ?? [], + ); +} + +const GetPluginDataInput = z.object({ + playerId: z.string().optional(), + pluginId: z.string(), + dataKey: z.string(), +}); + +export function handleGetPluginData( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId, pluginId, dataKey } = GetPluginDataInput.parse(input); + const resolved = resolvePlayer(client, playerId); + if ("error" in resolved) return err(resolved.error); + const plugin = resolved.player.plugins[pluginId]; + if (!plugin) return err(`plugin not found: ${pluginId}`); + const value = (plugin.flow?.data as Record | undefined)?.[ + dataKey + ]; + return ok(value ?? null); +} diff --git a/devtools/mcp/src/tools/index.ts b/devtools/mcp/src/tools/index.ts new file mode 100644 index 0000000..adc1b96 --- /dev/null +++ b/devtools/mcp/src/tools/index.ts @@ -0,0 +1,4 @@ +export * from "./flow"; +export * from "./players"; +export * from "./plugins"; +export * from "./select"; diff --git a/devtools/mcp/src/tools/players.ts b/devtools/mcp/src/tools/players.ts new file mode 100644 index 0000000..d45e1a0 --- /dev/null +++ b/devtools/mcp/src/tools/players.ts @@ -0,0 +1,72 @@ +import type { ExtensionClient } from "@player-devtools/client"; +import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; + +export const listPlayersTool: Tool = { + name: "list_players", + description: + "List all Player instances known to the devtools, and which one is currently selected.", + inputSchema: { type: "object", properties: {}, required: [] }, +}; + +export const getPlayerStatusTool: Tool = { + name: "get_player_status", + description: + "Get the active status and registered plugin IDs for a Player instance.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + }, + required: [], + }, +}; + +const GetPlayerStatusInput = z.object({ playerId: z.string().optional() }); + +export function handleListPlayers(client: ExtensionClient): CallToolResult { + const { players, current } = client.getState(); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + players: Object.keys(players), + current: current.player, + }), + }, + ], + }; +} + +export function handleGetPlayerStatus( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId } = GetPlayerStatusInput.parse(input); + const { players, current } = client.getState(); + const id = playerId ?? current.player; + if (!id) return errorResult("no player selected"); + const player = players[id]; + if (!player) return errorResult(`player not found: ${id}`); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + active: player.active, + plugins: Object.keys(player.plugins), + }), + }, + ], + }; +} + +function errorResult(message: string): CallToolResult { + return { + content: [{ type: "text", text: JSON.stringify({ error: message }) }], + }; +} diff --git a/devtools/mcp/src/tools/plugins.ts b/devtools/mcp/src/tools/plugins.ts new file mode 100644 index 0000000..4baa104 --- /dev/null +++ b/devtools/mcp/src/tools/plugins.ts @@ -0,0 +1,52 @@ +import type { ExtensionClient } from "@player-devtools/client"; +import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; + +export const describePluginTool: Tool = { + name: "describe_plugin", + description: + "Get the capability descriptor declared by a plugin at registration time. Use this to discover what data keys and actions the plugin exposes.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + pluginId: { type: "string", description: "Plugin ID." }, + }, + required: ["pluginId"], + }, +}; + +const DescribePluginInput = z.object({ + playerId: z.string().optional(), + pluginId: z.string(), +}); + +function err(message: string): CallToolResult { + return { + content: [{ type: "text", text: JSON.stringify({ error: message }) }], + }; +} + +function ok(value: unknown): CallToolResult { + return { content: [{ type: "text", text: JSON.stringify(value) }] }; +} + +export function handleDescribePlugin( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId, pluginId } = DescribePluginInput.parse(input); + const { players, current } = client.getState(); + const id = playerId ?? current.player; + if (!id) return err("no player selected"); + const player = players[id]; + if (!player) return err(`player not found: ${id}`); + const plugin = player.plugins[pluginId]; + if (!plugin) return err(`plugin not found: ${pluginId}`); + if (!plugin.capabilities) + return err(`plugin "${pluginId}" has no capabilities declared`); + return ok(plugin.capabilities); +} diff --git a/devtools/mcp/src/tools/select.ts b/devtools/mcp/src/tools/select.ts new file mode 100644 index 0000000..27f3d4b --- /dev/null +++ b/devtools/mcp/src/tools/select.ts @@ -0,0 +1,88 @@ +import type { ExtensionClient } from "@player-devtools/client"; +import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; + +export const selectPlayerTool: Tool = { + name: "select_player", + description: + "Select a Player instance as the active target for subsequent tool calls.", + inputSchema: { + type: "object", + properties: { + playerId: { type: "string", description: "The Player ID to select." }, + }, + required: ["playerId"], + }, +}; + +export const invokeActionTool: Tool = { + name: "invoke_action", + description: + "Invoke a named action on a plugin. Use describe_plugin first to discover available actions.", + inputSchema: { + type: "object", + properties: { + playerId: { + type: "string", + description: "Player ID. Defaults to the currently selected player.", + }, + pluginId: { type: "string", description: "Plugin ID." }, + action: { type: "string", description: "Action name." }, + payload: { type: "string", description: "Optional stringified payload." }, + }, + required: ["pluginId", "action"], + }, +}; + +const SelectPlayerInput = z.object({ playerId: z.string() }); + +const InvokeActionInput = z.object({ + playerId: z.string().optional(), + pluginId: z.string(), + action: z.string(), + payload: z.string().optional(), +}); + +function err(message: string): CallToolResult { + return { + content: [{ type: "text", text: JSON.stringify({ error: message }) }], + }; +} + +function ok(value: unknown): CallToolResult { + return { content: [{ type: "text", text: JSON.stringify(value) }] }; +} + +export function handleSelectPlayer( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId } = SelectPlayerInput.parse(input); + const { players } = client.getState(); + if (!players[playerId]) return err(`player not found: ${playerId}`); + client.selectPlayer(playerId); + return ok({ selected: playerId }); +} + +export function handleInvokeAction( + client: ExtensionClient, + input: unknown, +): CallToolResult { + const { playerId, pluginId, action, payload } = + InvokeActionInput.parse(input); + const { players, current } = client.getState(); + const id = playerId ?? current.player; + if (!id) return err("no player selected"); + const player = players[id]; + if (!player) return err(`player not found: ${id}`); + const plugin = player.plugins[pluginId]; + if (!plugin) return err(`plugin not found: ${pluginId}`); + if ( + plugin.capabilities?.actions && + !(action in plugin.capabilities.actions) + ) { + return err(`action "${action}" not declared in plugin capabilities`); + } + client.handleInteraction({ type: action, payload }); + return ok({ invoked: action }); +} diff --git a/devtools/mcp/src/transport.ts b/devtools/mcp/src/transport.ts new file mode 100644 index 0000000..7638fb9 --- /dev/null +++ b/devtools/mcp/src/transport.ts @@ -0,0 +1,347 @@ +import { + createFlipperServer, + FlipperServerState, + type FlipperServer, +} from "flipper-server-client"; +import { WebSocketServer, type WebSocket } from "ws"; +import { spawn, type ChildProcess } from "child_process"; +import * as net from "net"; +import type { + CommunicationLayerMethods, + ExtensionSupportedEvents, + MessengerEvent, + TransactionMetadata, +} from "@player-devtools/types"; + +type MessageCallback = ( + message: TransactionMetadata & MessengerEvent, +) => void; + +const PLUGIN_API = "player-ui-devtools"; + +/** Shape of a Flipper `client-message` payload after JSON.parse */ +type FlipperExecuteMessage = { + method: "execute"; + params: { + api: string; + method: string; + params?: unknown; + }; +}; + +/** Transport interface — implemented by each connection adapter */ +export interface Transport extends CommunicationLayerMethods { + /** Connect to the underlying transport */ + connect(): Promise; + /** Tear down the underlying transport */ + close(): Promise; +} + +/** + * Flipper headless transport + * + * Connects to a running `flipper-server` process and routes messages for the + * "flipper-plugin-player-ui-devtools" plugin through to the Messenger layer. + */ +/** Wait until a TCP port is accepting connections, polling every 500ms */ +function waitForPort( + host: string, + port: number, + timeoutMs = 30_000, +): Promise { + return new Promise((resolve, reject) => { + const deadline = Date.now() + timeoutMs; + const attempt = () => { + const socket = net.connect(port, host); + socket.once("connect", () => { + socket.destroy(); + resolve(); + }); + socket.once("error", () => { + socket.destroy(); + if (Date.now() >= deadline) { + reject(new Error(`Timed out waiting for ${host}:${port}`)); + } else { + setTimeout(attempt, 500); + } + }); + }; + attempt(); + }); +} + +export class FlipperServerTransport implements Transport { + private server: FlipperServer | null = null; + private flipperProcess: ChildProcess | null = null; + private listeners = new Set(); + + /** + * Client IDs that have sent at least one message through the devtools + * plugin — these are the clients we send outbound messages to. + */ + private activeClientIds = new Set(); + + constructor( + private options: { + /** Flipper server host; defaults to "localhost" */ + host?: string; + /** Flipper server WebSocket port; defaults to 52342 */ + port?: number; + } = {}, + ) {} + + async connect(): Promise { + const host = this.options.host ?? "localhost"; + const port = this.options.port ?? 52342; + + // Check if flipper-server is already listening; if not, start it + const alreadyRunning = await new Promise((resolve) => { + const socket = net.connect(port, host); + socket.once("connect", () => { + socket.destroy(); + resolve(true); + }); + socket.once("error", () => { + socket.destroy(); + resolve(false); + }); + }); + + if (!alreadyRunning) { + console.log("[FlipperServerTransport] Starting flipper-server..."); + const serverScript = require.resolve("flipper-server/server.js"); + this.flipperProcess = spawn( + process.execPath, + [serverScript, "--open=true"], + { + stdio: "inherit", + detached: false, + }, + ); + this.flipperProcess.on("error", (err) => { + console.error( + "[FlipperServerTransport] flipper-server process error:", + err, + ); + }); + // Kill the child synchronously on any form of exit so it doesn't orphan + process.on("exit", () => this.flipperProcess?.kill()); + await waitForPort(host, port); + console.log("[FlipperServerTransport] flipper-server ready."); + } else { + console.log("[FlipperServerTransport] flipper-server already running."); + } + + // Read the auth token the flipper-server wrote during startup + const { getAuthToken } = + // eslint-disable-next-line @typescript-eslint/no-require-imports + require("flipper-server/lib/app-connectivity/certificate-exchange/certificate-utils") as { + getAuthToken: () => Promise; + }; + + let cachedToken: string | null = null; + try { + cachedToken = await getAuthToken(); + } catch (err) { + console.warn("[FlipperServerTransport] Could not read auth token:", err); + } + + this.server = await createFlipperServer( + host, + port, + () => cachedToken, + (state) => { + if (state === FlipperServerState.DISCONNECTED) { + console.warn("[FlipperServerTransport] Disconnected from server"); + } + }, + ); + + await this.server.connect(); + + // Track client connects/disconnects + this.server.on("client-connected", (info) => { + console.log( + "[FlipperServerTransport] client-connected:", + JSON.stringify(info), + ); + }); + this.server.on("client-disconnected", ({ id }) => { + console.log("[FlipperServerTransport] client-disconnected:", id); + this.activeClientIds.delete(id); + }); + + // Route inbound device messages to our Messenger listeners + this.server.on("client-message", ({ id, message }) => { + let parsed: FlipperExecuteMessage; + try { + parsed = JSON.parse(message) as FlipperExecuteMessage; + } catch { + return; + } + + console.debug( + `[FlipperServerTransport] client-message from ${id}: method=${parsed.method} api=${(parsed.params as { api?: string })?.api} pluginMethod=${(parsed.params as { method?: string })?.method}`, + ); + + if ( + parsed.method !== "execute" || + parsed.params?.api !== PLUGIN_API || + parsed.params?.method !== "message::plugin" + ) { + return; + } + + // This client is talking through the devtools plugin — remember it + this.activeClientIds.add(id); + + const payload = parsed.params.params as TransactionMetadata & + MessengerEvent; + + for (const listener of this.listeners) { + listener(payload); + } + }); + } + + sendMessage: CommunicationLayerMethods["sendMessage"] = async (message) => { + if (!this.server) return; + + const payload: FlipperExecuteMessage = { + method: "execute", + params: { + api: PLUGIN_API, + method: "message::flipper", + params: message, + }, + }; + + await Promise.all( + [...this.activeClientIds].map((clientId) => + this.server!.exec("client-request-response", clientId, payload).catch( + (err) => { + console.warn( + `[FlipperServerTransport] Failed to send to client ${clientId}:`, + err, + ); + // Remove dead client so we stop trying + this.activeClientIds.delete(clientId); + }, + ), + ), + ); + }; + + addListener: CommunicationLayerMethods["addListener"] = (callback) => { + this.listeners.add(callback); + }; + + removeListener: CommunicationLayerMethods["removeListener"] = (callback) => { + this.listeners.delete(callback); + }; + + async close(): Promise { + this.listeners.clear(); + this.activeClientIds.clear(); + this.server?.close(); + this.server = null; + if (this.flipperProcess) { + this.flipperProcess.kill(); + this.flipperProcess = null; + } + } +} + +/** Default port the MCP server listens on for WebSocket connections */ +export const DEFAULT_WS_PORT = 7382; + +/** + * WebSocket server transport + * + * The MCP server opens a WebSocket server; the player connects as a client + * (via `useWSCommunicationLayer`). Works for: + * - Browser-based players + * - iOS/Android simulators over localhost + * - Physical devices over WiFi (same LAN) + */ +export class WebSocketServerTransport implements Transport { + private wss: WebSocketServer | null = null; + private clients = new Set(); + private listeners = new Set(); + + constructor( + private options: { + /** Port to listen on; defaults to 7382 */ + port?: number; + /** Host to bind to; defaults to "localhost" */ + host?: string; + } = {}, + ) {} + + async connect(): Promise { + const port = this.options.port ?? DEFAULT_WS_PORT; + const host = this.options.host ?? "localhost"; + + await new Promise((resolve, reject) => { + this.wss = new WebSocketServer({ port, host }); + + this.wss.on("listening", resolve); + this.wss.on("error", reject); + + this.wss.on("connection", (socket) => { + this.clients.add(socket); + + socket.on("message", (data) => { + let parsed: TransactionMetadata & + MessengerEvent; + try { + parsed = JSON.parse(data.toString()); + } catch { + return; + } + for (const listener of this.listeners) { + listener(parsed); + } + }); + + socket.on("close", () => { + this.clients.delete(socket); + }); + + socket.on("error", (err) => { + console.warn("[WebSocketServerTransport] Client error:", err); + this.clients.delete(socket); + }); + }); + }); + + console.log(`[WebSocketServerTransport] Listening on ws://${host}:${port}`); + } + + sendMessage: CommunicationLayerMethods["sendMessage"] = async (message) => { + const data = JSON.stringify(message); + for (const client of this.clients) { + if (client.readyState === 1 /* OPEN */) { + client.send(data); + } + } + }; + + addListener: CommunicationLayerMethods["addListener"] = (callback) => { + this.listeners.add(callback); + }; + + removeListener: CommunicationLayerMethods["removeListener"] = (callback) => { + this.listeners.delete(callback); + }; + + async close(): Promise { + for (const client of this.clients) { + client.close(); + } + this.clients.clear(); + this.listeners.clear(); + await new Promise((resolve) => this.wss?.close(() => resolve())); + this.wss = null; + } +} diff --git a/devtools/plugin/core/src/plugin.ts b/devtools/plugin/core/src/plugin.ts index c729921..7042cbf 100644 --- a/devtools/plugin/core/src/plugin.ts +++ b/devtools/plugin/core/src/plugin.ts @@ -10,7 +10,11 @@ import { import { dsetAssign } from "@player-devtools/utils"; import type { DataModel, Player, PlayerPlugin } from "@player-ui/player"; import { produce } from "immer"; -import { useStateReducer, type Store, type Unsubscribe } from "./state"; +import { + useStateReducer, + type Store, + type Unsubscribe, +} from "@player-devtools/utils"; import { reducer } from "./reducer"; import { PLUGIN_INACTIVE_WARNING, INTERACTIONS } from "./constants"; import { genDataChangeTransaction } from "./helpers"; diff --git a/devtools/plugin/core/src/state.ts b/devtools/plugin/core/src/state.ts deleted file mode 100644 index 71c0322..0000000 --- a/devtools/plugin/core/src/state.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type Reducer = (state: T, action: A) => T; -export type Dispatch = (action: A) => void; -export type Subscriber = (state: T) => void; -export type Subscribe = (subscriber: Subscriber) => Unsubscribe; -export type Unsubscribe = () => void; - -export interface Store { - getState: () => State; - subscribe: Subscribe; - dispatch: Dispatch; -} - -export const useStateReducer = ( - reducer: Reducer, - initialState: State, -): Store => { - let state = initialState; - const subscribers = new Set>(); - return { - getState: () => state, - - /** Subscribe to state changes; returns an unsubscribe function. */ - subscribe(subscriber: Subscriber): Unsubscribe { - subscribers.add(subscriber); - subscriber(state); - return () => subscribers.delete(subscriber); - }, - - /** Dispatch an action through the reducer, then run side-effects. */ - dispatch(action: Action): void { - const prevState = state; - const nextState = reducer(prevState, action); - - // Only proceed if state actually changed by reference - if (nextState !== prevState) { - state = nextState; - - // Notify subscribers - for (const sub of subscribers) sub(state); - } - }, - }; -}; diff --git a/devtools/utils/core/src/index.ts b/devtools/utils/core/src/index.ts index 531a1b7..fedf731 100644 --- a/devtools/utils/core/src/index.ts +++ b/devtools/utils/core/src/index.ts @@ -1,3 +1,44 @@ +export type Reducer = (state: State, action: Action) => State; +export type Dispatch = (action: Action) => void; +export type Subscriber = (state: State) => void; +export type Subscribe = (subscriber: Subscriber) => Unsubscribe; +export type Unsubscribe = () => void; + +export interface Store { + getState: () => State; + subscribe: Subscribe; + dispatch: Dispatch; +} + +export const useStateReducer = ( + reducer: Reducer, + initialState: State, +): Store => { + let state = initialState; + const subscribers = new Set>(); + return { + getState: () => state, + + /** Subscribe to state changes; returns an unsubscribe function. */ + subscribe(subscriber: Subscriber): Unsubscribe { + subscribers.add(subscriber); + subscriber(state); + return () => subscribers.delete(subscriber); + }, + + /** Dispatch an action through the reducer, then run side-effects. */ + dispatch(action: Action): void { + const prevState = state; + const nextState = reducer(prevState, action); + + if (nextState !== prevState) { + state = nextState; + for (const sub of subscribers) sub(state); + } + }, + }; +}; + /** * Recursively assigns the contents of `source` into `target`, preserving * existing object and array references to maintain key insertion order. diff --git a/justfile b/justfile index 4228b8d..4a346ab 100644 --- a/justfile +++ b/justfile @@ -74,6 +74,15 @@ install-flipper-client: rsync -a --delete bazel-bin/$PREFIX/$PLUGIN_NAME/. $INSTALL_LOCATION/ chown -R $(whoami) $INSTALL_LOCATION + +[doc('Run the MCP server (requires a running Flipper server on localhost:52342)')] +mcp: + bazel run //devtools/mcp:mcp_server + +[doc('Open MCP inspector against the MCP server')] +mcp-inspect: + bazel run //devtools/mcp:inspect + clean: # Force delete all the cached bazel stuff. Be careful! # Delete all the bazel build artifacts rm -rf .build diff --git a/package.json b/package.json index 7071f5e..c35f57f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "@devtools-ui/plugin": "0.4.0", "@emotion/styled": "^11", "@eslint/js": "^9.29.0", + "@modelcontextprotocol/inspector": "^0.21.1", + "@modelcontextprotocol/sdk": "^1.0.0", "@oclif/core": "1.9.0", "@oclif/errors": "^1.3.6", "@oclif/plugin-plugins": "^1.9.0", @@ -79,6 +81,7 @@ "@types/react-dom": "^18.3.0", "@types/std-mocks": "^1.0.4", "@types/uuid": "^8.3.4", + "@types/ws": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.34.0", "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-react": "^4.2.1", @@ -99,6 +102,8 @@ "eslint-plugin-react": "^7.37.4", "figures": "^3.0.0", "flipper-pkg": "^0.273.0", + "flipper-server": "^0.273.0", + "flipper-server-client": "^0.273.0", "flipper-plugin": "^0.273.0", "fs-extra": "^10.0.0", "globby": "^11.0.1", @@ -133,7 +138,9 @@ "vite-plugin-commonjs": "^0.10.1", "vitest": "^3.2.3", "vscode-languageserver-textdocument": "^1.0.11", - "vscode-languageserver-types": "^3.17.5" + "vscode-languageserver-types": "^3.17.5", + "ws": "^8.0.0", + "zod": "^3.0.0" }, "packageManager": "pnpm@10.12.1", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9cd1cc..e3fe20e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,12 @@ importers: '@eslint/js': specifier: ^9.29.0 version: 9.35.0 + '@modelcontextprotocol/inspector': + specifier: ^0.21.1 + version: 0.21.2(@swc/core@1.3.74)(@types/node@24.5.2)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(typescript@5.8.3) + '@modelcontextprotocol/sdk': + specifier: ^1.0.0 + version: 1.29.0(zod@3.25.76) '@oclif/core': specifier: 1.9.0 version: 1.9.0 @@ -205,6 +211,9 @@ importers: '@types/uuid': specifier: ^8.3.4 version: 8.3.4 + '@types/ws': + specifier: ^8.0.0 + version: 8.18.1 '@typescript-eslint/eslint-plugin': specifier: ^8.34.0 version: 8.44.0(@typescript-eslint/parser@5.62.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3) @@ -268,6 +277,12 @@ importers: flipper-plugin: specifier: ^0.273.0 version: 0.273.0(@ant-design/icons@4.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@testing-library/dom@9.3.4)(antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + flipper-server: + specifier: ^0.273.0 + version: 0.273.0 + flipper-server-client: + specifier: ^0.273.0 + version: 0.273.0 fs-extra: specifier: ^10.0.0 version: 10.1.0 @@ -370,6 +385,12 @@ importers: vscode-languageserver-types: specifier: ^3.17.5 version: 3.17.5 + ws: + specifier: ^8.0.0 + version: 8.18.3 + zod: + specifier: ^3.0.0 + version: 3.25.76 devtools/client: dependencies: @@ -392,6 +413,18 @@ importers: specifier: workspace:* version: link:../types/core + devtools/mcp: + dependencies: + '@player-devtools/client': + specifier: workspace:* + version: link:../client + '@player-devtools/messenger': + specifier: workspace:* + version: link:../messenger/core + '@player-devtools/types': + specifier: workspace:* + version: link:../types/core + devtools/messenger/core: dependencies: '@player-devtools/types': @@ -2730,6 +2763,12 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, tarball: https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==, tarball: https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, tarball: https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz} engines: {node: '>=18.18.0'} @@ -2746,6 +2785,9 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, tarball: https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz} engines: {node: '>=18.18'} + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==, tarball: https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz} + '@icons/material@0.2.4': resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==, tarball: https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz} peerDependencies: @@ -2998,11 +3040,58 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==, tarball: https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz} + '@mcp-ui/client@6.1.1': + resolution: {integrity: sha512-kN5io7ouEpVfOTloEEGrnuWio3ZzYOVgTk4o8ULleACdKEw7VgOTozPl9aj9qVl/UBh7rXlvKH0JbBm6Tw52LQ==, tarball: https://registry.npmjs.org/@mcp-ui/client/-/client-6.1.1.tgz} + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + '@mdx-js/react@2.3.0': resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==, tarball: https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz} peerDependencies: react: '>=16' + '@modelcontextprotocol/ext-apps@1.7.4': + resolution: {integrity: sha512-QQqysE549cf/Y0VabBmAACXhj92EhB3t8yVct2BHbkWiPTFA1S91EqTVjYXXcZEefXU0pmHcdObhsNMcomJIOQ==, tarball: https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.4.tgz} + engines: {node: '>=20'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.29.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@modelcontextprotocol/inspector-cli@0.21.2': + resolution: {integrity: sha512-Om2ApfIbkbfR7+PqJX0bO2Qwg0z55MgxquC+G0YL6x80ElpZMfxITsWQ1238kh3bv1zlwPSePc2kvDnCcU5tXw==, tarball: https://registry.npmjs.org/@modelcontextprotocol/inspector-cli/-/inspector-cli-0.21.2.tgz} + hasBin: true + + '@modelcontextprotocol/inspector-client@0.21.2': + resolution: {integrity: sha512-8us3hVXDgMHGb5jF1bBE/a6rRRlEaS+SKjbDFB56oE6+mNcAP9JgL8h6T0fYd3XQ3vL/CYxjVeQE+5yRdvhsVg==, tarball: https://registry.npmjs.org/@modelcontextprotocol/inspector-client/-/inspector-client-0.21.2.tgz} + hasBin: true + + '@modelcontextprotocol/inspector-server@0.21.2': + resolution: {integrity: sha512-ASFNPFMnT0Vn5u6aYRwwMrwA/0qpxb1lg3sE5GjWii5bUfPNXuIaLOXuXKSOFIMG4o82RxpuipdqX1ZdsmTPUw==, tarball: https://registry.npmjs.org/@modelcontextprotocol/inspector-server/-/inspector-server-0.21.2.tgz} + hasBin: true + + '@modelcontextprotocol/inspector@0.21.2': + resolution: {integrity: sha512-f/zIzl6ccYjIxSTgmol9EKvd8AXsXkIaZX16kLGhrYwxrApvU3IL6IzgOqATKP/2kgyKdFUOVLlHMxX9e6BZZA==, tarball: https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-0.21.2.tgz} + engines: {node: '>=22.7.5'} + hasBin: true + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==, tarball: https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@monaco-editor/loader@1.5.0': resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==, tarball: https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz} @@ -3473,12 +3562,18 @@ packages: '@radix-ui/number@1.0.1': resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==, tarball: https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz} + '@radix-ui/number@1.1.2': + resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==, tarball: https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz} + '@radix-ui/primitive@1.0.1': resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz} '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz} + '@radix-ui/primitive@1.1.4': + resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz} + '@radix-ui/react-arrow@1.0.3': resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==, tarball: https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz} peerDependencies: @@ -3492,6 +3587,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.10': + resolution: {integrity: sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==, tarball: https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.10.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.5': + resolution: {integrity: sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==, tarball: https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.5.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.0.3': resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==, tarball: https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz} peerDependencies: @@ -3505,6 +3626,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.10': + resolution: {integrity: sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==, tarball: https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.10.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==, tarball: https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz} peerDependencies: @@ -3536,6 +3670,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.0.1': resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==, tarball: https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz} peerDependencies: @@ -3554,6 +3697,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.4': + resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==, tarball: https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.17': + resolution: {integrity: sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==, tarball: https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.17.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.0.1': resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==, tarball: https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz} peerDependencies: @@ -3572,6 +3737,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==, tarball: https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.0.4': resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==, tarball: https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz} peerDependencies: @@ -3585,6 +3759,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.13': + resolution: {integrity: sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==, tarball: https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.13.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.0.1': resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz} peerDependencies: @@ -3594,6 +3781,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-focus-scope@1.0.3': resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.3.tgz} peerDependencies: @@ -3607,6 +3803,24 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-scope@1.1.10': + resolution: {integrity: sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.10.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==, tarball: https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.0.1': resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==, tarball: https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz} peerDependencies: @@ -3625,6 +3839,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==, tarball: https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.7': resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==, tarball: https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz} peerDependencies: @@ -3638,6 +3861,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.17': + resolution: {integrity: sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==, tarball: https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.17.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.1.2': resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==, tarball: https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz} peerDependencies: @@ -3651,6 +3887,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.3.1': + resolution: {integrity: sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==, tarball: https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.1.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.0.3': resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==, tarball: https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.3.tgz} peerDependencies: @@ -3664,6 +3913,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.12': + resolution: {integrity: sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==, tarball: https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.12.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.6': + resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==, tarball: https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@1.0.3': resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz} peerDependencies: @@ -3690,6 +3965,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.6': + resolution: {integrity: sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.6.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==, tarball: https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz} peerDependencies: @@ -3703,6 +3991,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.13': + resolution: {integrity: sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==, tarball: https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.13.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@1.2.2': resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==, tarball: https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz} peerDependencies: @@ -3716,6 +4017,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-select@2.3.1': + resolution: {integrity: sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==, tarball: https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.1.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-separator@1.1.7': resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==, tarball: https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz} peerDependencies: @@ -3747,6 +4061,54 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.3.1': + resolution: {integrity: sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw==, tarball: https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.1.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.15': + resolution: {integrity: sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==, tarball: https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.15.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.17': + resolution: {integrity: sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==, tarball: https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.17.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle-group@1.1.11': resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==, tarball: https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz} peerDependencies: @@ -3760,8 +4122,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toggle@1.1.10': - resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==, tarball: https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz} + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==, tarball: https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==, tarball: https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3773,8 +4148,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toolbar@1.1.11': - resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==, tarball: https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz} + '@radix-ui/react-tooltip@1.2.10': + resolution: {integrity: sha512-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw==, tarball: https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.10.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3804,6 +4179,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.0.1': resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz} peerDependencies: @@ -3822,6 +4206,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz} peerDependencies: @@ -3831,6 +4224,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.0.3': resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==, tarball: https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz} peerDependencies: @@ -3840,6 +4242,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-escape-keydown@1.1.2': + resolution: {integrity: sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.0.1': resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==, tarball: https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz} peerDependencies: @@ -3858,6 +4269,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-previous@1.0.1': resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz} peerDependencies: @@ -3867,6 +4287,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-previous@1.1.2': + resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-rect@1.0.1': resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz} peerDependencies: @@ -3876,6 +4305,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.2': + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-size@1.0.1': resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==, tarball: https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz} peerDependencies: @@ -3885,6 +4323,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-size@1.1.2': + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==, tarball: https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.0.3': resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==, tarball: https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz} peerDependencies: @@ -3898,9 +4345,25 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.6': + resolution: {integrity: sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==, tarball: https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.6.tgz} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.0.1': resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==, tarball: https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz} + '@radix-ui/rect@1.1.2': + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==, tarball: https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz} + '@rc-component/portal@1.1.2': resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==, tarball: https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz} engines: {node: '>=8.x'} @@ -4258,6 +4721,9 @@ packages: resolution: {integrity: sha512-PJBmyayrlfxM7nbqjomF4YcT1sApQwZio0NHSsT0EzhJqljRmvhzqZua43TyEs80nJk2Cn2FGPg/N8phH6KeCQ==, tarball: https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.1.1.tgz} engines: {node: '>=18.0.0'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, tarball: https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz} + '@storybook/addon-docs@7.6.20': resolution: {integrity: sha512-XNfYRhbxH5JP7B9Lh4W06PtMefNXkfpV39Gaoih5HuqngV3eoSL4RikZYOMkvxRGQ738xc6axySU3+JKcP1OZg==, tarball: https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.20.tgz} peerDependencies: @@ -4816,6 +5282,9 @@ packages: '@types/wrap-ansi@3.0.0': resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==, tarball: https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==, tarball: https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, tarball: https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz} @@ -5083,6 +5552,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, tarball: https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==, tarball: https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz} + engines: {node: '>= 0.6'} + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==, tarball: https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz} engines: {node: '>=10.13.0'} @@ -5112,6 +5585,23 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adbkit-logcat@1.1.0: + resolution: {integrity: sha512-57iYRLdjmhI1fnc890KyflzWpnIb/aq5ET3fbn3axdyyeyKeP4Ji/GhnfBNguG1Tw7SQRL2eBnA+hvbwIsTtNg==, tarball: https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.1.0.tgz} + engines: {node: '>= 0.10.4'} + + adbkit-logcat@2.0.1: + resolution: {integrity: sha512-MznVzzEzcrWhIaIyblll+a0AL1TICJe/yuaia7HDYTAtiNabR/9amJkAnLt30U8/W7MVBc3mvU1jB/6MJ/TYHw==, tarball: https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-2.0.1.tgz} + engines: {node: '>= 4'} + + adbkit-monkey@1.0.1: + resolution: {integrity: sha512-uU8p+p4sv7gLsjO/At4iPufoPD3R16kVbzDVecdIerR9RzhEK6PcyAJghdOXwrYKbhm7SmPQWsxVRloDBMINDA==, tarball: https://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz} + engines: {node: '>= 0.10.4'} + + adbkit@2.11.1: + resolution: {integrity: sha512-hDTiRg9NX3HQt7WoDAPCplUpvzr4ZzQa2lq7BdTTJ/iOZ6O7YNAs6UYD8sFAiBEcYHDRIyq3cm9sZP6uZnhvXw==, tarball: https://registry.npmjs.org/adbkit/-/adbkit-2.11.1.tgz} + engines: {node: '>= 0.10.4'} + hasBin: true + address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==, tarball: https://registry.npmjs.org/address/-/address-1.2.2.tgz} engines: {node: '>= 10.0.0'} @@ -5141,6 +5631,14 @@ packages: ajv: optional: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, tarball: https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==, tarball: https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz} peerDependencies: @@ -5214,6 +5712,18 @@ packages: app-root-dir@1.0.2: resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==, tarball: https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==, tarball: https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==, tarball: https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==, tarball: https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz} + engines: {node: '>= 10'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, tarball: https://registry.npmjs.org/arg/-/arg-4.1.3.tgz} @@ -5320,12 +5830,18 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==, tarball: https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz} + async-mutex@0.3.2: + resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==, tarball: https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz} + async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==, tarball: https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz} async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==, tarball: https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz} + async@0.2.10: + resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==, tarball: https://registry.npmjs.org/async/-/async-0.2.10.tgz} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==, tarball: https://registry.npmjs.org/async/-/async-3.2.6.tgz} @@ -5356,6 +5872,9 @@ packages: axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==, tarball: https://registry.npmjs.org/axios/-/axios-0.21.4.tgz} + axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==, tarball: https://registry.npmjs.org/axios/-/axios-0.26.1.tgz} + babel-core@7.0.0-bridge.0: resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==, tarball: https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz} peerDependencies: @@ -5429,10 +5948,17 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, tarball: https://registry.npmjs.org/bl/-/bl-4.1.0.tgz} + bluebird@2.9.34: + resolution: {integrity: sha512-ZDzCb87X7/IP1uzQ5eJZB+WoQRGTnKL5DHWvPw6kkMbQseouiQIrEi3P1UGE0D1k0N5/+aP/5GMCyHZ1xYJyHQ==, tarball: https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz} + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==, tarball: https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==, tarball: https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz} + engines: {node: '>=18'} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==, tarball: https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz} @@ -5484,6 +6010,9 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==, tarball: https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==, tarball: https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz} + buffer-fill@1.0.0: resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==, tarball: https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz} @@ -5493,12 +6022,20 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, tarball: https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==, tarball: https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz} + engines: {node: '>=18'} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==, tarball: https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.18' + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==, tarball: https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz} + engines: {node: '>= 0.8'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, tarball: https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz} engines: {node: '>= 0.8'} @@ -5682,6 +6219,10 @@ packages: cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==, tarball: https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, tarball: https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz} + engines: {node: '>=12'} + clone-deep@4.0.1: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==, tarball: https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz} engines: {node: '>=6'} @@ -5702,6 +6243,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, tarball: https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz} engines: {node: '>=6'} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==, tarball: https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + codemirror@6.0.2: resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==, tarball: https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz} @@ -5742,6 +6289,10 @@ packages: resolution: {integrity: sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==, tarball: https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz} engines: {node: '>=8.0.0'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, tarball: https://registry.npmjs.org/commander/-/commander-13.1.0.tgz} + engines: {node: '>=18'} + commander@2.13.0: resolution: {integrity: sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==, tarball: https://registry.npmjs.org/commander/-/commander-2.13.0.tgz} @@ -5763,6 +6314,10 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==, tarball: https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==, tarball: https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz} + engines: {node: '>= 10'} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==, tarball: https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz} engines: {node: '>= 0.6'} @@ -5784,6 +6339,11 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==, tarball: https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz} engines: {'0': node >= 0.8} + concurrently@9.2.3: + resolution: {integrity: sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==, tarball: https://registry.npmjs.org/concurrently/-/concurrently-9.2.3.tgz} + engines: {node: '>=18'} + hasBin: true + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, tarball: https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz} @@ -5801,14 +6361,26 @@ packages: constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==, tarball: https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz} + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==, tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz} + engines: {node: '>= 0.6'} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==, tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz} engines: {node: '>= 0.6'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==, tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, tarball: https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==, tarball: https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz} + engines: {node: '>=18'} + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==, tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz} @@ -5818,6 +6390,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==, tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==, tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz} + engines: {node: '>=6.6.0'} + cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==, tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz} engines: {node: '>= 0.6'} @@ -5831,6 +6407,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==, tarball: https://registry.npmjs.org/cors/-/cors-2.8.6.tgz} + engines: {node: '>= 0.10'} + cosmiconfig@5.2.1: resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==, tarball: https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz} engines: {node: '>=4'} @@ -5843,6 +6423,15 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==, tarball: https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz} engines: {node: '>=10'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==, tarball: https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==, tarball: https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz} + engines: {node: '>= 10'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, tarball: https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz} @@ -5876,6 +6465,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, tarball: https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==, tarball: https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz} + engines: {node: '>= 12'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, tarball: https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz} engines: {node: '>= 0.4'} @@ -5969,6 +6562,14 @@ packages: resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==, tarball: https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz} engines: {node: '>=12'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==, tarball: https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==, tarball: https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz} + engines: {node: '>=18'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, tarball: https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz} @@ -5984,6 +6585,10 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==, tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz} engines: {node: '>=8'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, tarball: https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz} engines: {node: '>= 0.4'} @@ -6123,6 +6728,9 @@ packages: resolution: {integrity: sha512-uW2UKSsuty9ANJ3YByIQE4ANkD8nqUPO7r6Fwcc1ADKPe9FRdcPpMl3VEput4JSvKBJ4J86npIC2MLP0pYkCuw==, tarball: https://registry.npmjs.org/ebnf/-/ebnf-1.9.1.tgz} hasBin: true + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, tarball: https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, tarball: https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz} @@ -6599,18 +7207,40 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, tarball: https://registry.npmjs.org/events/-/events-3.3.0.tgz} engines: {node: '>=0.8.x'} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==, tarball: https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==, tarball: https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, tarball: https://registry.npmjs.org/execa/-/execa-5.1.1.tgz} engines: {node: '>=10'} + exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==, tarball: https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz} + engines: {node: '>=6'} + expect-type@1.2.2: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==, tarball: https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz} engines: {node: '>=12.0.0'} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==, tarball: https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==, tarball: https://registry.npmjs.org/express/-/express-4.21.2.tgz} engines: {node: '>= 0.10.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==, tarball: https://registry.npmjs.org/express/-/express-5.2.1.tgz} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, tarball: https://registry.npmjs.org/extend/-/extend-3.0.2.tgz} @@ -6688,6 +7318,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==, tarball: https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz} + engines: {node: ^12.20 || >= 14.13} + fetch-retry@5.0.6: resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==, tarball: https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz} @@ -6703,6 +7337,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, tarball: https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz} engines: {node: '>=16.0.0'} + file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==, tarball: https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz} + file-system-cache@2.3.0: resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==, tarball: https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz} @@ -6733,6 +7370,10 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==, tarball: https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz} engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==, tarball: https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz} + engines: {node: '>= 18.0.0'} + find-cache-dir@2.1.0: resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==, tarball: https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz} engines: {node: '>=6'} @@ -6798,6 +7439,13 @@ packages: '@testing-library/dom': ^7.26.3 antd: ^4.24 + flipper-server-client@0.273.0: + resolution: {integrity: sha512-9v6sly59LgOfdMKQVcVHkiGYcGs1xTrtZz7A9ATKP8HMG3OXYGDNuk9P6bUMY+QY2T+8E29FeXTgLDMfM2fQow==, tarball: https://registry.npmjs.org/flipper-server-client/-/flipper-server-client-0.273.0.tgz} + + flipper-server@0.273.0: + resolution: {integrity: sha512-EjRFoSlJQ9dcaHCpxICTVsbam4auKUTDfjTvzJofD6/YvfbGxUi39uUwH9M3+sBqQLpn5yvYaPyUTmU6XhX9VA==, tarball: https://registry.npmjs.org/flipper-server/-/flipper-server-0.273.0.tgz} + hasBin: true + flow-bin@0.118.0: resolution: {integrity: sha512-jlbUu0XkbpXeXhan5xyTqVK1jmEKNxE8hpzznI3TThHTr76GiFwK0iRzhDo4KNy+S9h/KxHaqVhTP86vA6wHCg==, tarball: https://registry.npmjs.org/flow-bin/-/flow-bin-0.118.0.tgz} engines: {node: '>=0.10.0'} @@ -6845,6 +7493,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==, tarball: https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==, tarball: https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz} + engines: {node: '>=12.20.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, tarball: https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz} engines: {node: '>= 0.6'} @@ -6873,6 +7525,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==, tarball: https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==, tarball: https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz} + engines: {node: '>= 0.8'} + fromentries@1.3.2: resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==, tarball: https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz} @@ -7110,6 +7766,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, tarball: https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz} + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==, tarball: https://registry.npmjs.org/hono/-/hono-4.12.27.tgz} + engines: {node: '>=16.9.0'} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, tarball: https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz} @@ -7135,6 +7795,14 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==, tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==, tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz} + engines: {node: '>= 0.8'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==, tarball: https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz} + engines: {node: '>=8.0.0'} + http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==, tarball: https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz} engines: {node: '>=10.19.0'} @@ -7171,6 +7839,10 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==, tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, tarball: https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz} @@ -7245,6 +7917,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, tarball: https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz} engines: {node: '>= 0.4'} + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==, tarball: https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz} + engines: {node: '>= 0.10'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==, tarball: https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz} @@ -7253,6 +7929,10 @@ packages: peerDependencies: fp-ts: ^2.5.0 + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==, tarball: https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz} + engines: {node: '>= 12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, tarball: https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz} engines: {node: '>= 0.10'} @@ -7319,6 +7999,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, tarball: https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, tarball: https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz} engines: {node: '>=0.10.0'} @@ -7343,6 +8028,11 @@ packages: resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==, tarball: https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, tarball: https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz} + engines: {node: '>=14.16'} + hasBin: true + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, tarball: https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz} engines: {node: '>=8'} @@ -7394,6 +8084,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, tarball: https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz} engines: {node: '>=0.10.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==, tarball: https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, tarball: https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz} engines: {node: '>= 0.4'} @@ -7454,6 +8147,10 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz} engines: {node: '>=8'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz} + engines: {node: '>=16'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, tarball: https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz} @@ -7551,6 +8248,9 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==, tarball: https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==, tarball: https://registry.npmjs.org/jose/-/jose-6.2.3.tgz} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==, tarball: https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz} engines: {node: '>=10'} @@ -7618,6 +8318,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==, tarball: https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz} + json-source-map@0.6.1: resolution: {integrity: sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==, tarball: https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz} @@ -7648,10 +8351,20 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==, tarball: https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz} engines: {'0': node >= 0.2.0} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==, tarball: https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, tarball: https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz} engines: {node: '>=4.0'} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==, tarball: https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==, tarball: https://registry.npmjs.org/jws/-/jws-4.0.1.tgz} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, tarball: https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz} @@ -7682,6 +8395,10 @@ packages: resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==, tarball: https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz} engines: {node: '>=14.0.0'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==, tarball: https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz} + engines: {node: '>= 0.6.3'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, tarball: https://registry.npmjs.org/leven/-/leven-3.1.0.tgz} engines: {node: '>=6'} @@ -7766,6 +8483,15 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, tarball: https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==, tarball: https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==, tarball: https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==, tarball: https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz} + lodash.flow@3.5.0: resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==, tarball: https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz} @@ -7773,12 +8499,36 @@ packages: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==, tarball: https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz} deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==, tarball: https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==, tarball: https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==, tarball: https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==, tarball: https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, tarball: https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==, tarball: https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, tarball: https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, tarball: https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz} lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==, tarball: https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==, tarball: https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, tarball: https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz} @@ -7792,6 +8542,9 @@ packages: lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, tarball: https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==, tarball: https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, tarball: https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz} @@ -7831,6 +8584,11 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 + lucide-react@0.523.0: + resolution: {integrity: sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==, tarball: https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz} hasBin: true @@ -7940,15 +8698,27 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, tarball: https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==, tarball: https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz} + engines: {node: '>= 0.8'} + memoize-one@3.1.1: resolution: {integrity: sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==, tarball: https://registry.npmjs.org/memoize-one/-/memoize-one-3.1.1.tgz} memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==, tarball: https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz} + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==, tarball: https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz} + engines: {node: '>= 0.10.0'} + merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==, tarball: https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==, tarball: https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, tarball: https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz} @@ -8110,6 +8880,10 @@ packages: microseconds@0.2.0: resolution: {integrity: sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==, tarball: https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz} + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==, tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz} + engines: {node: '>= 0.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz} engines: {node: '>= 0.6'} @@ -8118,10 +8892,18 @@ packages: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz} engines: {node: '>= 0.6'} + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==, tarball: https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, tarball: https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==, tarball: https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, tarball: https://registry.npmjs.org/mime/-/mime-1.6.0.tgz} engines: {node: '>=4'} @@ -8155,6 +8937,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz} engines: {node: '>=10'} @@ -8264,6 +9049,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==, tarball: https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==, tarball: https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, tarball: https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz} @@ -8277,6 +9066,11 @@ packages: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==, tarball: https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz} engines: {node: '>= 0.10.5'} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==, tarball: https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==, tarball: https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz} @@ -8298,6 +9092,17 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==, tarball: https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-forge@0.10.0: + resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==, tarball: https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz} + engines: {node: '>= 6.0.0'} + + node-forge@0.7.6: + resolution: {integrity: sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==, tarball: https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, tarball: https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz} @@ -8421,10 +9226,17 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, tarball: https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz} engines: {node: '>=6'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==, tarball: https://registry.npmjs.org/open/-/open-10.2.0.tgz} + engines: {node: '>=18'} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==, tarball: https://registry.npmjs.org/open/-/open-8.4.2.tgz} engines: {node: '>=12'} + openssl-wrapper@0.3.4: + resolution: {integrity: sha512-iITsrx6Ho8V3/2OVtmZzzX8wQaKAaFXEJQdzoPUZDtyf5jWFlqo+h+OhGT4TATQ47f9ACKHua8nw7Qoy85aeKQ==, tarball: https://registry.npmjs.org/openssl-wrapper/-/openssl-wrapper-0.3.4.tgz} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, tarball: https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz} engines: {node: '>= 0.8.0'} @@ -8567,6 +9379,9 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, tarball: https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz} engines: {node: '>=0.10.0'} + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==, tarball: https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, tarball: https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz} engines: {node: '>=8'} @@ -8581,6 +9396,12 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz} + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz} + path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==, tarball: https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz} engines: {node: '>=4'} @@ -8637,6 +9458,14 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, tarball: https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz} engines: {node: '>= 6'} + pkce-challenge@4.1.0: + resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==, tarball: https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz} + engines: {node: '>=16.20.0'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==, tarball: https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz} + engines: {node: '>=16.20.0'} + pkg-conf@2.1.0: resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==, tarball: https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz} engines: {node: '>=4'} @@ -8723,6 +9552,10 @@ packages: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==, tarball: https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz} engines: {node: '>=10'} + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==, tarball: https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz} + engines: {node: '>=6'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, tarball: https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz} @@ -8737,6 +9570,10 @@ packages: promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==, tarball: https://registry.npmjs.org/promise/-/promise-7.3.1.tgz} + promisify-child-process@4.1.2: + resolution: {integrity: sha512-APnkIgmaHNJpkAn7k+CrJSi9WMuff5ctYFbD0CO2XIPkM8yO7d/ShouU2clywbpHV/DUsyc4bpJCsNgddNtx4g==, tarball: https://registry.npmjs.org/promisify-child-process/-/promisify-child-process-4.1.2.tgz} + engines: {node: '>=8'} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==, tarball: https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz} engines: {node: '>= 6'} @@ -8782,6 +9619,10 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==, tarball: https://registry.npmjs.org/qs/-/qs-6.14.0.tgz} engines: {node: '>=0.6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==, tarball: https://registry.npmjs.org/qs/-/qs-6.15.3.tgz} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz} @@ -8795,6 +9636,10 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, tarball: https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz} + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==, tarball: https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz} + engines: {node: '>= 0.6'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, tarball: https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz} engines: {node: '>= 0.6'} @@ -8803,6 +9648,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==, tarball: https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==, tarball: https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz} + engines: {node: '>= 0.10'} + rc-align@4.0.15: resolution: {integrity: sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==, tarball: https://registry.npmjs.org/rc-align/-/rc-align-4.0.15.tgz} peerDependencies: @@ -9219,6 +10068,22 @@ packages: '@types/react': optional: true + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==, tarball: https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-simple-code-editor@0.14.1: + resolution: {integrity: sha512-BR5DtNRy+AswWJECyA17qhUDvrrCZ6zXOCfkQY5zSmb96BVUbpVAv03WpcjcwtCwiLbIANx3gebHOcXYn1EHow==, tarball: https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.14.1.tgz} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==, tarball: https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz} engines: {node: '>=10'} @@ -9283,6 +10148,9 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, tarball: https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz} engines: {node: '>= 6'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==, tarball: https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, tarball: https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz} engines: {node: '>= 14.18.0'} @@ -9291,6 +10159,13 @@ packages: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==, tarball: https://registry.npmjs.org/recast/-/recast-0.23.11.tgz} engines: {node: '>= 4'} + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==, tarball: https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz} + engines: {node: '>= 0.10'} + + reconnecting-websocket@4.4.0: + resolution: {integrity: sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==, tarball: https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz} + recursive-readdir@2.2.3: resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==, tarball: https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz} engines: {node: '>=6.0.0'} @@ -9381,6 +10256,9 @@ packages: resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==, tarball: https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz} engines: {node: '>= 4.0.0'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==, tarball: https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz} + reselect@4.1.8: resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==, tarball: https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz} @@ -9463,9 +10341,41 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==, tarball: https://registry.npmjs.org/router/-/router-2.2.0.tgz} + engines: {node: '>= 18'} + + rsocket-core@0.0.25: + resolution: {integrity: sha512-rKqT+Vku86fIW2hFlkIRz/ak4jvoxVIbVs00l299y6hEWEChWI6weyvrJvlqCQmzbvcmq0/ZEFXa8e5F/yZ8zQ==, tarball: https://registry.npmjs.org/rsocket-core/-/rsocket-core-0.0.25.tgz} + + rsocket-core@0.0.27: + resolution: {integrity: sha512-q/+8PY0BIPvwXb6jPsnKCdyUKFusrm4JDzWnxbW1Ywu2sG/suv28/+mHC7kRGtMnRsxsLAWnqsmI6EznTabItA==, tarball: https://registry.npmjs.org/rsocket-core/-/rsocket-core-0.0.27.tgz} + + rsocket-flowable@0.0.25: + resolution: {integrity: sha512-ZlMna0EP/ytS6WR3tvV1YfE0p5UN9S5x6sepZiwa8qwXlYHH9HqjAIs1N5hMp5FuxUpgIGrzTyCPcnji/NX1dw==, tarball: https://registry.npmjs.org/rsocket-flowable/-/rsocket-flowable-0.0.25.tgz} + + rsocket-flowable@0.0.27: + resolution: {integrity: sha512-I/1vETXP+qCfGFeMY8i+QLhyBpcEj0nDmWnP2vh7lDyOBNT9LjAiY6kigUzEHbfhoNe3vnJWYK6uCzxIsi622Q==, tarball: https://registry.npmjs.org/rsocket-flowable/-/rsocket-flowable-0.0.27.tgz} + + rsocket-tcp-client@0.0.25: + resolution: {integrity: sha512-NDvnhobNMGX78Bhc2NqbyODsv5w5KUmKLsCR1dCVMqhsEB1atB0B4d/wl3EgaIBskinKOH2t9AXl7ptDz6Cgfg==, tarball: https://registry.npmjs.org/rsocket-tcp-client/-/rsocket-tcp-client-0.0.25.tgz} + + rsocket-tcp-server@0.0.25: + resolution: {integrity: sha512-JFgrZH//5ViHgNqSjJnvApWyJ1jfVzQZZVXX3xOZFiYQuqsqVVl9poR+r03O5i9UM+amrliVQhIDaUBbfR6N9A==, tarball: https://registry.npmjs.org/rsocket-tcp-server/-/rsocket-tcp-server-0.0.25.tgz} + + rsocket-types@0.0.25: + resolution: {integrity: sha512-YSz0Ll9thEATVObc4CJ3+DD+nlfY/s7u2P7sdKJUhMxrBuMLOmUypweXguxUAi1D6UfOr+ZJaxek7K8rBiFT0w==, tarball: https://registry.npmjs.org/rsocket-types/-/rsocket-types-0.0.25.tgz} + + rsocket-types@0.0.27: + resolution: {integrity: sha512-88vgA+d31a6wW5YP63jQZ0CqZ3DALtH4oCbuMtIcNy28m1kDYGPZunhsnw+YC/XtjOqb5hKhn10ZQm7OK2xc1g==, tarball: https://registry.npmjs.org/rsocket-types/-/rsocket-types-0.0.27.tgz} + rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==, tarball: https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==, tarball: https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz} + engines: {node: '>=18'} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==, tarball: https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz} engines: {node: '>=0.12.0'} @@ -9542,6 +10452,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==, tarball: https://registry.npmjs.org/send/-/send-0.19.0.tgz} engines: {node: '>= 0.8.0'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==, tarball: https://registry.npmjs.org/send/-/send-1.2.1.tgz} + engines: {node: '>= 18'} + sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==, tarball: https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz} @@ -9549,13 +10463,24 @@ packages: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==, tarball: https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz} engines: {node: '>=0.10.0'} + serialize-error@8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==, tarball: https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz} + engines: {node: '>=10'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==, tarball: https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz} + serve-handler@6.1.7: + resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==, tarball: https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==, tarball: https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz} engines: {node: '>= 0.8.0'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==, tarball: https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, tarball: https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz} @@ -9600,10 +10525,32 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, tarball: https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz} engines: {node: '>=8'} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==, tarball: https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz} + engines: {node: '>= 0.4'} + + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==, tarball: https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz} + engines: {node: '>= 0.4'} + + shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==, tarball: https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz} + engines: {node: '>=4'} + hasBin: true + + shx@0.3.4: + resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==, tarball: https://registry.npmjs.org/shx/-/shx-0.3.4.tgz} + engines: {node: '>=6'} + hasBin: true + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, tarball: https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz} engines: {node: '>= 0.4'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==, tarball: https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz} + engines: {node: '>= 0.4'} + side-channel-map@1.0.1: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, tarball: https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz} engines: {node: '>= 0.4'} @@ -9616,6 +10563,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, tarball: https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz} @@ -9694,6 +10645,9 @@ packages: space-separated-tokens@1.1.5: resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==, tarball: https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz} + spawn-rx@5.1.2: + resolution: {integrity: sha512-/y7tJKALVZ1lPzeZZB9jYnmtrL7d0N2zkorii5a7r7dhHkWIuLTzZpZzMJLK1dmYRgX/NCc4iarTO3F7BS2c/A==, tarball: https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.2.tgz} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, tarball: https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz} @@ -9706,6 +10660,13 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==, tarball: https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==, tarball: https://registry.npmjs.org/split2/-/split2-4.2.0.tgz} + engines: {node: '>= 10.x'} + + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==, tarball: https://registry.npmjs.org/split/-/split-0.3.3.tgz} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, tarball: https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz} @@ -9735,6 +10696,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==, tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==, tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz} + engines: {node: '>= 0.8'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, tarball: https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz} @@ -10186,6 +11151,10 @@ packages: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==, tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz} engines: {node: '>=10'} + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz} + engines: {node: '>=10'} + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz} engines: {node: '>=10'} @@ -10210,6 +11179,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, tarball: https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==, tarball: https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, tarball: https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz} engines: {node: '>= 0.4'} @@ -10588,6 +11561,10 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, tarball: https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==, tarball: https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz} @@ -10726,6 +11703,26 @@ packages: utf-8-validate: optional: true + ws@8.6.0: + resolution: {integrity: sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==, tarball: https://registry.npmjs.org/ws/-/ws-8.6.0.tgz} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==, tarball: https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz} + engines: {node: '>=18'} + + xdg-basedir@4.0.0: + resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==, tarball: https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz} + engines: {node: '>=8'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, tarball: https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz} engines: {node: '>=0.4'} @@ -10733,6 +11730,10 @@ packages: y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==, tarball: https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, tarball: https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, tarball: https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz} @@ -10747,10 +11748,22 @@ packages: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==, tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz} engines: {node: '>=6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz} + engines: {node: '>=12'} + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==, tarball: https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz} engines: {node: '>=8'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, tarball: https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==, tarball: https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz} + engines: {node: '>=12'} + yarn@1.22.22: resolution: {integrity: sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==, tarball: https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz} engines: {node: '>=4.0.0'} @@ -10771,6 +11784,18 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==, tarball: https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz} engines: {node: '>=18'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==, tarball: https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz} + engines: {node: '>= 10'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==, tarball: https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, tarball: https://registry.npmjs.org/zod/-/zod-3.25.76.tgz} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, tarball: https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz} @@ -14120,6 +15145,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hono/node-server@1.19.14(hono@4.12.27)': + dependencies: + hono: 4.12.27 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -14131,6 +15160,8 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iarna/toml@2.2.5': {} + '@icons/material@0.2.4(react@18.3.1)': dependencies: react: 18.3.1 @@ -14434,12 +15465,142 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@mcp-ui/client@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@modelcontextprotocol/ext-apps': 1.7.4(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zod: 3.25.76 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + '@mdx-js/react@2.3.0(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 '@types/react': 18.3.24 react: 18.3.1 + '@modelcontextprotocol/ext-apps@1.7.4(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@standard-schema/spec': 1.1.0 + zod: 3.25.76 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@modelcontextprotocol/inspector-cli@0.21.2(zod@3.25.76)': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + commander: 13.1.0 + express: 5.2.1 + spawn-rx: 5.1.2 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + - zod + + '@modelcontextprotocol/inspector-client@0.21.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)': + dependencies: + '@mcp-ui/client': 6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@modelcontextprotocol/ext-apps': 1.7.4(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@radix-ui/react-checkbox': 1.3.5(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-icons': 1.3.2(react@18.3.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 2.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-switch': 1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': 1.2.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ajv: 6.12.6 + class-variance-authority: 0.7.1 + clsx: 2.1.1 + cmdk: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: 0.523.0(react@18.3.1) + pkce-challenge: 4.1.0 + prismjs: 1.30.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-simple-code-editor: 0.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + serve-handler: 6.1.7 + tailwind-merge: 2.6.0 + zod: 3.25.76 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/react' + - '@types/react-dom' + - supports-color + + '@modelcontextprotocol/inspector-server@0.21.2': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + cors: 2.8.6 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + shell-quote: 1.9.0 + shx: 0.3.4 + spawn-rx: 5.1.2 + ws: 8.18.3 + zod: 3.25.76 + transitivePeerDependencies: + - '@cfworker/json-schema' + - bufferutil + - supports-color + - utf-8-validate + + '@modelcontextprotocol/inspector@0.21.2(@swc/core@1.3.74)(@types/node@24.5.2)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(typescript@5.8.3)': + dependencies: + '@modelcontextprotocol/inspector-cli': 0.21.2(zod@3.25.76) + '@modelcontextprotocol/inspector-client': 0.21.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24) + '@modelcontextprotocol/inspector-server': 0.21.2 + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + concurrently: 9.2.3 + node-fetch: 3.3.2 + open: 10.2.0 + shell-quote: 1.9.0 + spawn-rx: 5.1.2 + ts-node: 10.9.2(@swc/core@1.3.74)(@types/node@24.5.2)(typescript@5.8.3) + zod: 3.25.76 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - '@types/react' + - '@types/react-dom' + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.27) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.27 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@monaco-editor/loader@1.5.0': dependencies: state-local: 1.0.7 @@ -15485,12 +16646,16 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 + '@radix-ui/number@1.1.2': {} + '@radix-ui/primitive@1.0.1': dependencies: '@babel/runtime': 7.28.4 '@radix-ui/primitive@1.1.3': {} + '@radix-ui/primitive@1.1.4': {} + '@radix-ui/react-arrow@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15501,6 +16666,31 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-arrow@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-checkbox@1.3.5(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15514,6 +16704,18 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-collection@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.24)(react@18.3.1) @@ -15539,6 +16741,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-compose-refs@1.1.3(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-context@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15552,6 +16760,34 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-context@1.1.4(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + + '@radix-ui/react-dialog@1.1.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.24)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-direction@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15565,6 +16801,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-direction@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15579,6 +16821,19 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-dismissable-layer@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15586,6 +16841,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-focus-guards@1.1.4(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-focus-scope@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15598,6 +16859,21 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-focus-scope@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-icons@1.3.2(react@18.3.1)': + dependencies: + react: 18.3.1 + '@radix-ui/react-id@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15613,6 +16889,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-id@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-label@2.1.7(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -15622,6 +16905,29 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-popover@1.1.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.24)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-popper@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15641,6 +16947,24 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-popper@1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/rect': 1.1.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-portal@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15651,6 +16975,25 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-portal@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-presence@1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15670,6 +17013,15 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-primitive@2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -15687,6 +17039,23 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-roving-focus@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-direction': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-select@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15712,7 +17081,37 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.3.24)(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@18.3.24)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-select@2.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.2 + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-direction': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.24)(react@18.3.1) optionalDependencies: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) @@ -15741,6 +17140,64 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-slot@1.3.0(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + + '@radix-ui/react-switch@1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-tabs@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-direction': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + + '@radix-ui/react-toast@1.2.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -15782,6 +17239,26 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-tooltip@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-context': 1.1.4(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-popper': 1.3.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.3.0(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15795,6 +17272,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15811,6 +17294,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.24)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.24)(react@18.3.1) @@ -15818,6 +17309,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-effect-event@0.0.3(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15826,6 +17324,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-escape-keydown@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15839,6 +17344,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-previous@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15846,6 +17357,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-previous@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-rect@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15854,6 +17371,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-rect@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-use-size@1.0.1(@types/react@18.3.24)(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15862,6 +17386,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-size@1.1.2(@types/react@18.3.24)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@18.3.24)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.24 + '@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -15872,10 +17403,21 @@ snapshots: '@types/react': 18.3.24 '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/react-visually-hidden@1.2.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@radix-ui/rect@1.0.1': dependencies: '@babel/runtime': 7.28.4 + '@radix-ui/rect@1.1.2': {} + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.28.4 @@ -16309,6 +17851,8 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 + '@standard-schema/spec@1.1.0': {} + '@storybook/addon-docs@7.6.20(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@jest/transform': 29.7.0 @@ -17255,6 +18799,10 @@ snapshots: '@types/wrap-ansi@3.0.0': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 24.5.2 + '@types/yargs-parser@21.0.3': {} '@types/yargs@15.0.19': @@ -17652,6 +19200,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -17674,6 +19227,26 @@ snapshots: acorn@8.15.0: {} + adbkit-logcat@1.1.0: {} + + adbkit-logcat@2.0.1: {} + + adbkit-monkey@1.0.1: + dependencies: + async: 0.2.10 + + adbkit@2.11.1: + dependencies: + adbkit-logcat: 1.1.0 + adbkit-monkey: 1.0.1 + bluebird: 2.9.34 + commander: 2.20.3 + debug: 2.6.9 + node-forge: 0.7.6 + split: 0.3.3 + transitivePeerDependencies: + - supports-color + address@1.2.2: {} agent-base@5.1.1: {} @@ -17697,6 +19270,10 @@ snapshots: optionalDependencies: ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-keywords@5.1.0(ajv@8.17.1): dependencies: ajv: 8.17.1 @@ -17819,6 +19396,42 @@ snapshots: app-root-dir@1.0.2: {} + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + arg@4.1.3: {} argparse@1.0.10: @@ -17940,12 +19553,18 @@ snapshots: async-limiter@1.0.1: {} + async-mutex@0.3.2: + dependencies: + tslib: 2.8.1 + async-retry@1.3.3: dependencies: retry: 0.13.1 async-validator@4.2.5: {} + async@0.2.10: {} + async@3.2.6: {} asynckit@0.4.0: {} @@ -17988,6 +19607,12 @@ snapshots: transitivePeerDependencies: - debug + axios@0.26.1: + dependencies: + follow-redirects: 1.15.11 + transitivePeerDependencies: + - debug + babel-core@7.0.0-bridge.0(@babel/core@7.28.4): dependencies: '@babel/core': 7.28.4 @@ -18098,6 +19723,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bluebird@2.9.34: {} + body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -18115,6 +19742,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + bottleneck@2.19.5: {} bowser@2.12.1: {} @@ -18182,6 +19823,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} + buffer-fill@1.0.0: {} buffer-from@1.1.2: {} @@ -18191,11 +19834,17 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bundle-require@5.1.0(esbuild@0.25.10): dependencies: esbuild: 0.25.10 load-tsconfig: 0.2.5 + bytes@3.0.0: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -18434,6 +20083,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone-deep@4.0.1: dependencies: is-plain-object: 2.0.4 @@ -18448,6 +20103,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.17(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.24)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + codemirror@6.0.2: dependencies: '@codemirror/autocomplete': 6.18.7 @@ -18505,6 +20172,8 @@ snapshots: table-layout: 1.0.2 typical: 5.2.0 + commander@13.1.0: {} + commander@2.13.0: {} commander@2.20.3: {} @@ -18517,6 +20186,13 @@ snapshots: commondir@1.0.1: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -18546,6 +20222,15 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 + concurrently@9.2.3: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.8.4 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + confbox@0.1.8: {} config-chain@1.1.13: @@ -18570,18 +20255,26 @@ snapshots: tslib: 2.8.1 upper-case: 2.0.2 + content-disposition@0.5.2: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 + content-disposition@1.1.0: {} + content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} + cookie-signature@1.2.2: {} + cookie@0.7.1: {} copy-to-clipboard@3.3.3: @@ -18594,6 +20287,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@5.2.1: dependencies: import-fresh: 2.0.0 @@ -18617,6 +20315,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + create-require@1.1.1: {} crelt@1.0.6: {} @@ -18652,6 +20357,8 @@ snapshots: csstype@3.1.3: {} + data-uri-to-buffer@4.0.1: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -18770,6 +20477,13 @@ snapshots: bplist-parser: 0.2.0 untildify: 4.0.0 + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -18784,6 +20498,8 @@ snapshots: define-lazy-prop@2.0.0: {} + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -18910,6 +20626,10 @@ snapshots: ebnf@1.9.1: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} ejs@3.1.10: @@ -19484,6 +21204,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -19496,8 +21222,15 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + exit-hook@2.2.1: {} + expect-type@1.2.2: {} + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + express@4.21.2: dependencies: accepts: 1.3.8 @@ -19534,6 +21267,39 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.1 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} extract-stack@2.0.0: {} @@ -19617,6 +21383,11 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fetch-retry@5.0.6: {} figures@2.0.0: @@ -19631,6 +21402,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-stream-rotator@0.6.1: + dependencies: + moment: 2.30.1 + file-system-cache@2.3.0: dependencies: fs-extra: 11.1.1 @@ -19674,6 +21449,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + find-cache-dir@2.1.0: dependencies: commondir: 1.0.1 @@ -19814,19 +21600,79 @@ snapshots: flipper-common: 0.273.0 immer: 9.0.21 js-base64: 3.7.8 - lodash: 4.17.21 - react-color: 2.19.3(react@18.3.1) - react-element-to-jsx-string: 14.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-json-view: 1.21.3(@types/react@17.0.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-json-view-compare: 2.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-use: 17.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-virtual: 2.10.4(react@18.3.1) - string-natural-compare: 3.0.1 + lodash: 4.17.21 + react-color: 2.19.3(react@18.3.1) + react-element-to-jsx-string: 14.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-json-view: 1.21.3(@types/react@17.0.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-json-view-compare: 2.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-use: 17.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-virtual: 2.10.4(react@18.3.1) + string-natural-compare: 3.0.1 + transitivePeerDependencies: + - encoding + - react + - react-dom + - supports-color + + flipper-server-client@0.273.0: + dependencies: + eventemitter3: 4.0.7 + flipper-common: 0.273.0 + reconnecting-websocket: 4.4.0 + + flipper-server@0.273.0: + dependencies: + '@iarna/toml': 2.2.5 + JSONStream: 1.3.5 + adbkit: 2.11.1 + adbkit-logcat: 2.0.1 + archiver: 5.3.2 + async-mutex: 0.3.2 + axios: 0.26.1 + chalk: 4.1.2 + envinfo: 7.14.0 + exit-hook: 2.2.1 + express: 4.21.2 + fb-watchman: 2.0.2 + file-stream-rotator: 0.6.1 + flipper-common: 0.273.0 + flipper-pkg-lib: 0.273.0(patch_hash=663409f34b29aa343185d69669e0de3b6cea83783d25f46a1ec4b747359a44f3) + flipper-plugin-lib: 0.273.0 + form-data: 4.0.4 + fs-extra: 11.3.2 + http-proxy: 1.18.1 + immer: 9.0.21 + invariant: 2.2.4 + js-base64: 3.7.8 + jsonwebtoken: 9.0.3 + lodash.memoize: 4.1.2 + memorystream: 0.3.1 + metro: 0.70.4 + node-fetch: 2.7.0 + node-forge: 0.10.0 + open: 8.4.2 + openssl-wrapper: 0.3.4 + p-filter: 2.1.0 + promisify-child-process: 4.1.2 + reconnecting-websocket: 4.4.0 + rimraf: 3.0.2 + rsocket-core: 0.0.27 + rsocket-flowable: 0.0.27 + rsocket-tcp-server: 0.0.25 + rsocket-types: 0.0.25 + semver: 7.7.2 + serialize-error: 8.1.0 + split2: 4.2.0 + tmp: 0.2.5 + ws: 8.6.0 + xdg-basedir: 4.0.0 + yargs: 17.7.3 transitivePeerDependencies: + - bufferutil + - debug - encoding - - react - - react-dom - supports-color + - utf-8-validate flow-bin@0.118.0: {} @@ -19869,6 +21715,10 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + forwarded@0.2.0: {} fp-ts@2.16.11: {} @@ -19889,6 +21739,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fromentries@1.3.2: {} fs-constants@1.0.0: {} @@ -20170,6 +22022,8 @@ snapshots: dependencies: react-is: 16.13.1 + hono@4.12.27: {} + hosted-git-info@2.8.9: {} hosted-git-info@7.0.2: @@ -20201,6 +22055,22 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.11 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -20236,6 +22106,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore-walk@4.0.1: @@ -20315,6 +22189,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + interpret@1.4.0: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -20323,6 +22199,8 @@ snapshots: dependencies: fp-ts: 2.16.11 + ip-address@10.2.0: {} + ipaddr.js@1.9.1: {} is-absolute-url@3.0.3: {} @@ -20384,6 +22262,8 @@ snapshots: is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -20405,6 +22285,10 @@ snapshots: is-gzip@1.0.0: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@1.0.0: {} is-map@2.0.3: {} @@ -20439,6 +22323,8 @@ snapshots: is-plain-object@5.0.0: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -20492,6 +22378,10 @@ snapshots: dependencies: is-docker: 2.2.1 + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -20640,6 +22530,8 @@ snapshots: jiti@2.5.1: {} + jose@6.2.3: {} + joycon@3.1.1: {} js-base64@3.7.8: {} @@ -20706,6 +22598,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-source-map@0.6.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -20734,6 +22628,19 @@ snapshots: jsonparse@1.3.1: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.2 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -20741,6 +22648,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -20769,6 +22687,10 @@ snapshots: dotenv: 16.6.1 dotenv-expand: 10.0.0 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leven@3.1.0: {} levn@0.4.1: @@ -20883,14 +22805,36 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.flatten@4.4.0: {} + lodash.flow@3.5.0: {} lodash.get@4.4.2: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} lodash.mergewith@4.6.2: {} + lodash.once@4.1.1: {} + lodash.sortby@4.7.0: {} lodash.template@4.5.0: @@ -20904,6 +22848,8 @@ snapshots: lodash.throttle@4.1.1: {} + lodash.union@4.6.0: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -20942,6 +22888,10 @@ snapshots: dependencies: react: 18.3.1 + lucide-react@0.523.0(react@18.3.1): + dependencies: + react: 18.3.1 + lz-string@1.5.0: {} magic-string@0.27.0: @@ -21095,14 +23045,20 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + memoize-one@3.1.1: {} memoizerific@1.11.3: dependencies: map-or-similar: 1.5.0 + memorystream@0.3.1: {} + merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -21530,14 +23486,24 @@ snapshots: microseconds@0.2.0: {} + mime-db@1.33.0: {} + mime-db@1.52.0: {} mime-db@1.54.0: {} + mime-types@2.1.18: + dependencies: + mime-db: 1.33.0 + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.6.0: {} @@ -21558,6 +23524,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.2 @@ -21656,6 +23626,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} nested-error-stacks@2.0.1: {} @@ -21669,6 +23641,8 @@ snapshots: dependencies: minimatch: 3.1.2 + node-domexception@1.0.0: {} + node-fetch-native@1.6.7: {} node-fetch@2.6.7: @@ -21679,6 +23653,16 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-forge@0.10.0: {} + + node-forge@0.7.6: {} + node-int64@0.4.0: {} node-releases@2.0.21: {} @@ -21844,12 +23828,21 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 + openssl-wrapper@0.3.4: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -21991,6 +23984,8 @@ snapshots: path-is-absolute@1.0.1: {} + path-is-inside@1.0.2: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -22002,6 +23997,10 @@ snapshots: path-to-regexp@0.1.12: {} + path-to-regexp@3.3.0: {} + + path-to-regexp@8.4.2: {} + path-type@3.0.0: dependencies: pify: 3.0.0 @@ -22040,6 +24039,10 @@ snapshots: pirates@4.0.7: {} + pkce-challenge@4.1.0: {} + + pkce-challenge@5.0.1: {} + pkg-conf@2.1.0: dependencies: find-up: 2.1.0 @@ -22115,6 +24118,8 @@ snapshots: dependencies: parse-ms: 2.1.0 + prismjs@1.30.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -22125,6 +24130,8 @@ snapshots: dependencies: asap: 2.0.6 + promisify-child-process@4.1.2: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -22190,6 +24197,11 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -22200,6 +24212,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.0: {} + range-parser@1.2.1: {} raw-body@2.5.2: @@ -22209,6 +24223,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + rc-align@4.0.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.28.4 @@ -22760,6 +24781,22 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + react-remove-scroll@2.7.2(@types/react@18.3.24)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.24)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.24)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.24)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.24)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.24 + + react-simple-code-editor@0.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-style-singleton@2.2.3(@types/react@18.3.24)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -22851,6 +24888,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@4.1.2: {} recast@0.23.11: @@ -22861,6 +24902,12 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + rechoir@0.6.2: + dependencies: + resolve: 1.22.10 + + reconnecting-websocket@4.4.0: {} + recursive-readdir@2.2.3: dependencies: minimatch: 3.1.2 @@ -22976,6 +25023,8 @@ snapshots: rc: 1.2.8 resolve: 1.7.1 + requires-port@1.0.0: {} + reselect@4.1.8: {} resize-observer-polyfill@1.5.1: {} @@ -23064,10 +25113,73 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.50.2 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + rsocket-core@0.0.25: + dependencies: + fbjs: 3.0.5 + rsocket-flowable: 0.0.25 + rsocket-types: 0.0.25 + transitivePeerDependencies: + - encoding + + rsocket-core@0.0.27: + dependencies: + rsocket-flowable: 0.0.27 + rsocket-types: 0.0.27 + + rsocket-flowable@0.0.25: + dependencies: + fbjs: 3.0.5 + transitivePeerDependencies: + - encoding + + rsocket-flowable@0.0.27: {} + + rsocket-tcp-client@0.0.25: + dependencies: + fbjs: 3.0.5 + rsocket-core: 0.0.25 + rsocket-flowable: 0.0.25 + rsocket-types: 0.0.25 + transitivePeerDependencies: + - encoding + + rsocket-tcp-server@0.0.25: + dependencies: + fbjs: 3.0.5 + rsocket-core: 0.0.25 + rsocket-flowable: 0.0.25 + rsocket-tcp-client: 0.0.25 + rsocket-types: 0.0.25 + transitivePeerDependencies: + - encoding + + rsocket-types@0.0.25: + dependencies: + fbjs: 3.0.5 + rsocket-flowable: 0.0.25 + transitivePeerDependencies: + - encoding + + rsocket-types@0.0.27: + dependencies: + rsocket-flowable: 0.0.27 + rtl-css-js@1.16.1: dependencies: '@babel/runtime': 7.28.4 + run-applescript@7.1.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -23159,6 +25271,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + sentence-case@3.0.4: dependencies: no-case: 3.0.4 @@ -23167,10 +25295,24 @@ snapshots: serialize-error@2.1.0: {} + serialize-error@8.1.0: + dependencies: + type-fest: 0.20.2 + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + serve-handler@6.1.7: + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + mime-types: 2.1.18 + minimatch: 3.1.5 + path-is-inside: 1.0.2 + path-to-regexp: 3.3.0 + range-parser: 1.2.0 + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -23180,6 +25322,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -23226,11 +25377,31 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.4: {} + + shell-quote@1.9.0: {} + + shelljs@0.8.5: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + + shx@0.3.4: + dependencies: + minimist: 1.2.8 + shelljs: 0.8.5 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-map@1.0.1: dependencies: call-bound: 1.0.4 @@ -23254,6 +25425,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -23325,6 +25504,13 @@ snapshots: space-separated-tokens@1.1.5: {} + spawn-rx@5.1.2: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + rxjs: 7.8.2 + transitivePeerDependencies: + - supports-color + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -23339,6 +25525,12 @@ snapshots: spdx-license-ids@3.0.22: {} + split2@4.2.0: {} + + split@0.3.3: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} stack-generator@2.0.10: @@ -23366,6 +25558,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + std-env@3.9.0: {} std-mocks@1.0.1: @@ -23874,6 +26068,8 @@ snapshots: type-fest@0.16.0: {} + type-fest@0.20.2: {} + type-fest@0.21.3: {} type-fest@0.3.1: {} @@ -23889,6 +26085,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -24278,6 +26480,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} @@ -24437,10 +26641,20 @@ snapshots: ws@8.18.3: {} + ws@8.6.0: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + xdg-basedir@4.0.0: {} + xtend@4.0.2: {} y18n@4.0.3: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} @@ -24452,6 +26666,8 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -24466,6 +26682,26 @@ snapshots: y18n: 4.0.3 yargs-parser: 18.1.3 + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yarn@1.22.22: {} yauzl@2.10.0: @@ -24479,4 +26715,16 @@ snapshots: yoctocolors-cjs@2.1.3: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8c4c43a..2f082e9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,7 @@ packages: - docs/* - devtools/client - devtools/flipper-plugin + - devtools/mcp - devtools/plugin/core - devtools/plugin/react - devtools/plugins/basic/content From 6f5e6724cf9a3c755805934bc0892dd4051549e3 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 7 Apr 2026 23:11:14 -0700 Subject: [PATCH 2/5] re-add plugin capabilities --- devtools/plugins/basic/content/src/index.ts | 41 ++++++++++++++++++++- devtools/types/core/src/index.ts | 15 ++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/devtools/plugins/basic/content/src/index.ts b/devtools/plugins/basic/content/src/index.ts index 1f8abb9..87c9728 100644 --- a/devtools/plugins/basic/content/src/index.ts +++ b/devtools/plugins/basic/content/src/index.ts @@ -1,5 +1,5 @@ import type { PluginData } from "@player-devtools/types"; -import { PLUGIN_ID } from "./constants"; +import { PLUGIN_ID, INTERACTIONS } from "./constants"; // Generated via dsl_compile target import flow from "../_generated/flow.json"; @@ -17,6 +17,45 @@ export const BasicPluginData: PluginData = { description: "Standard Player UI Devtools", version: PLUGIN_VERSION, flow, + capabilities: { + description: + "Exposes Player runtime state (flow, data, logs, config) and supports expression evaluation and flow overrides.", + data: { + flow: { description: "The currently active Player flow JSON" }, + data: { description: "The current Player data model" }, + logs: { + description: "Accumulated log messages emitted by the Player runtime", + }, + playerConfig: { + description: "Player version and list of registered plugin names", + }, + history: { + description: + "Results of previous expression evaluations in this session", + }, + }, + actions: { + [INTERACTIONS.EVALUATE_EXPRESSION]: { + description: + "Evaluate a Player expression string and return the result", + params: { + payload: { + type: "string", + description: "The expression to evaluate", + }, + }, + }, + [INTERACTIONS.OVERRIDE_FLOW]: { + description: "Replace the currently running flow with a new flow JSON", + params: { + payload: { + type: "string", + description: "Stringified flow JSON to load", + }, + }, + }, + }, + }, }; export * from "./constants"; diff --git a/devtools/types/core/src/index.ts b/devtools/types/core/src/index.ts index d57a8e4..7c28777 100644 --- a/devtools/types/core/src/index.ts +++ b/devtools/types/core/src/index.ts @@ -97,6 +97,19 @@ export interface MessengerOptions> { }; } +/** Describes data keys and actions a plugin exposes to agents */ +export type PluginCapabilities = { + /** Human-readable description of what this plugin does */ + description: string; + /** Data keys the plugin publishes, keyed by name */ + data: Record; + /** Actions the plugin accepts, keyed by name */ + actions?: Record< + string, + { description: string; params?: Record } + >; +}; + /** Plugin data */ export interface PluginData { /** Plugin id */ @@ -109,6 +122,8 @@ export interface PluginData { description: string; /** Plugin UI */ flow: Flow; + /** Agent-visible capabilities declared at registration time */ + capabilities?: PluginCapabilities; } export interface ExtensionState { From d33655578c18ff5a52ca40152ce446077ca97503 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Mon, 29 Jun 2026 04:53:12 -0700 Subject: [PATCH 3/5] Fix devtools messenger routing and harden MCP tool wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Messenger sequencing fixes (verified by isolated revert + regression tests): - Broadcasts (id === -1) no longer advance the receiver's messagesReceived; otherwise a following targeted message looked like a duplicate and was dropped. - Stop double-incrementing messagesSent in sendMessage — getTransactionID already stamps and increments it, so the second bump desynced ids and triggered spurious lost-event recovery. - Advance the plugin interaction cursor before processing so a synchronous re-entrant dispatch (SELECTED_PLAYER_CHANGE) doesn't reprocess the same interaction. MCP server: - invoke_action now addresses the resolved playerId via target so an explicit player isn't misrouted to the currently selected one; handleInteraction takes an optional target. - Migrate tools to a declarative ToolDef table registered via McpServer; share Zod input shapes across the player-scoped tools. - Refcount the shared flipper-server daemon across MCP processes; drop the unused WebSocketServerTransport. Add regression tests for messenger target routing, plugin re-entrancy, and invoke_action routing. --- devtools/client/src/state/client.ts | 13 +- devtools/mcp/BUILD | 5 +- devtools/mcp/package.json | 3 + devtools/mcp/src/index.ts | 7 +- devtools/mcp/src/server.ts | 106 ++----- .../invokeAction.integration.test.ts | 208 +++++++++++++ .../mcp/src/tools/__tests__/select.test.ts | 69 +++++ devtools/mcp/src/tools/flow.ts | 121 +++----- devtools/mcp/src/tools/index.ts | 35 +++ devtools/mcp/src/tools/players.ts | 46 +-- devtools/mcp/src/tools/plugins.ts | 38 ++- devtools/mcp/src/tools/select.ts | 69 ++--- devtools/mcp/src/transport.ts | 280 ++++++++++-------- .../core/src/__tests__/index.test.ts | 207 ++++++++++++- devtools/messenger/core/src/index.ts | 15 +- .../plugin/core/src/__tests__/plugin.test.ts | 95 ++++++ devtools/plugin/core/src/plugin.ts | 13 +- pnpm-lock.yaml | 4 + 18 files changed, 954 insertions(+), 380 deletions(-) create mode 100644 devtools/mcp/src/tools/__tests__/invokeAction.integration.test.ts create mode 100644 devtools/mcp/src/tools/__tests__/select.test.ts create mode 100644 devtools/plugin/core/src/__tests__/plugin.test.ts diff --git a/devtools/client/src/state/client.ts b/devtools/client/src/state/client.ts index 2637b54..c9e9873 100644 --- a/devtools/client/src/state/client.ts +++ b/devtools/client/src/state/client.ts @@ -16,7 +16,11 @@ export type ExtensionClient = { subscribe: (fn: (state: ExtensionState) => void) => () => void; selectPlayer: (playerID: string) => void; selectPlugin: (pluginID: string) => void; - handleInteraction: (interaction: { type: string; payload?: string }) => void; + handleInteraction: (interaction: { + type: string; + payload?: string; + target?: string; + }) => void; destroy: () => void; }; @@ -67,15 +71,18 @@ export const createExtensionClient = ( const handleInteraction = ({ type, payload, + target, }: { type: string; payload?: string; + /** Player to address; defaults to the currently selected player. */ + target?: string; }): void => { - const { current } = store.getState(); + const resolvedTarget = target ?? store.getState().current.player; messenger.sendMessage({ type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", payload: { type, payload }, - ...(current.player ? { target: current.player } : {}), + ...(resolvedTarget ? { target: resolvedTarget } : {}), }); }; diff --git a/devtools/mcp/BUILD b/devtools/mcp/BUILD index b72c000..98e2cb5 100644 --- a/devtools/mcp/BUILD +++ b/devtools/mcp/BUILD @@ -15,16 +15,17 @@ deps = [ ":node_modules/@player-devtools/messenger", ":node_modules/@player-devtools/types", "//:node_modules/@modelcontextprotocol/sdk", - "//:node_modules/@types/ws", "//:node_modules/flipper-server", "//:node_modules/flipper-server-client", - "//:node_modules/ws", "//:node_modules/zod", ] js_pipeline( package_name = "@player-devtools/mcp", deps = deps, + test_deps = [ + ":node_modules/@player-devtools/plugin", + ], srcs = glob(["src/**/*", "bin/**/*"]) ) diff --git a/devtools/mcp/package.json b/devtools/mcp/package.json index 7aa4f43..8961d11 100644 --- a/devtools/mcp/package.json +++ b/devtools/mcp/package.json @@ -13,5 +13,8 @@ "@player-devtools/client": "workspace:*", "@player-devtools/messenger": "workspace:*", "@player-devtools/types": "workspace:*" + }, + "devDependencies": { + "@player-devtools/plugin": "workspace:*" } } diff --git a/devtools/mcp/src/index.ts b/devtools/mcp/src/index.ts index 3edeadb..66684f8 100644 --- a/devtools/mcp/src/index.ts +++ b/devtools/mcp/src/index.ts @@ -1,7 +1,2 @@ export { MCPServer } from "./server"; -export { - type Transport, - FlipperServerTransport, - WebSocketServerTransport, - DEFAULT_WS_PORT, -} from "./transport"; +export { type Transport, FlipperServerTransport } from "./transport"; diff --git a/devtools/mcp/src/server.ts b/devtools/mcp/src/server.ts index 3433b47..05308f1 100644 --- a/devtools/mcp/src/server.ts +++ b/devtools/mcp/src/server.ts @@ -2,99 +2,41 @@ import { createExtensionClient, type ExtensionClient, } from "@player-devtools/client"; -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import type { Transport } from "./transport"; -import { - listPlayersTool, - getPlayerStatusTool, - getFlowTool, - getDataTool, - getLogsTool, - getPluginDataTool, - describePluginTool, - selectPlayerTool, - invokeActionTool, - handleListPlayers, - handleGetPlayerStatus, - handleGetFlow, - handleGetData, - handleGetLogs, - handleGetPluginData, - handleDescribePlugin, - handleSelectPlayer, - handleInvokeAction, -} from "./tools"; - -const ALL_TOOLS = [ - listPlayersTool, - getPlayerStatusTool, - getFlowTool, - getDataTool, - getLogsTool, - getPluginDataTool, - describePluginTool, - selectPlayerTool, - invokeActionTool, -]; +import { TOOL_DEFS, type ToolDef } from "./tools"; export class MCPServer { private client: ExtensionClient; - private server: Server; + private server: McpServer; constructor(private transport: Transport) { this.client = createExtensionClient(transport); - this.server = new Server( - { name: "player-devtools", version: "0.0.1" }, - { capabilities: { tools: {} } }, - ); - this.registerHandlers(); + this.server = new McpServer({ name: "player-devtools", version: "0.0.1" }); + this.registerTools(); } - private registerHandlers(): void { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: ALL_TOOLS, - })); - - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - const c = this.client; - - switch (name) { - case "list_players": - return handleListPlayers(c); - case "get_player_status": - return handleGetPlayerStatus(c, args); - case "get_flow": - return handleGetFlow(c, args); - case "get_data": - return handleGetData(c, args); - case "get_logs": - return handleGetLogs(c, args); - case "get_plugin_data": - return handleGetPluginData(c, args); - case "describe_plugin": - return handleDescribePlugin(c, args); - case "select_player": - return handleSelectPlayer(c, args); - case "invoke_action": - return handleInvokeAction(c, args); - default: - return { - content: [ - { - type: "text" as const, - text: JSON.stringify({ error: `unknown tool: ${name}` }), - }, - ], - }; - } - }); + private registerTools(): void { + for (const def of TOOL_DEFS) { + // `registerTool` is heavily generic over the Zod input shape; inferring + // that across a heterogeneous array trips TS2589 (excessively deep). The + // shapes are validated at runtime by the SDK, so register through a + // loosely-typed view to keep inference shallow. + const register = this.server.registerTool.bind(this.server) as ( + name: string, + config: { description: string; inputSchema: ToolDef["inputSchema"] }, + cb: (args: unknown) => CallToolResult, + ) => unknown; + + register( + def.name, + { description: def.description, inputSchema: def.inputSchema }, + (args) => def.handle(this.client, args), + ); + } } async start(): Promise { diff --git a/devtools/mcp/src/tools/__tests__/invokeAction.integration.test.ts b/devtools/mcp/src/tools/__tests__/invokeAction.integration.test.ts new file mode 100644 index 0000000..97e242e --- /dev/null +++ b/devtools/mcp/src/tools/__tests__/invokeAction.integration.test.ts @@ -0,0 +1,208 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { Messenger } from "@player-devtools/messenger"; +import { DevtoolsPlugin, type DevtoolsHandler } from "@player-devtools/plugin"; +import type { + CommunicationLayerMethods, + ExtensionSupportedEvents, + MessengerOptions, + PluginData, + Transaction, +} from "@player-devtools/types"; + +// NOTE: this test drives the devtools/MCP side with a real Messenger rather +// than `createExtensionClient`, because that factory currently lives in the +// `@player-devtools/client` barrel alongside the React `Panel`, which can't be +// imported in this node test env (it pulls in UI-only CJS deps). The MCP +// handler's playerId→target resolution is unit-tested in select.test.ts; what +// THIS test proves is the other half: real DevtoolsPlugins on a shared bus only +// handle actions addressed to their own playerID. The frame built below is +// exactly what `client.handleInteraction({ target })` emits. + +vi.useFakeTimers(); + +type Frame = Transaction; + +const PLUGIN_ID = "test-plugin"; + +function pluginData(): PluginData { + return { + id: PLUGIN_ID, + version: "1.0.0", + name: "Test Plugin", + description: "test", + flow: { + id: "flow", + views: [], + navigation: {}, + } as unknown as PluginData["flow"], + capabilities: { + description: "test", + actions: { next: { description: "advance" } }, + data: {}, + }, + }; +} + +/** + * An in-memory message bus shared by every Messenger created against it. + * Mirrors how the browser extension / Flipper relay every frame to all + * connected parties — the transport is a dumb broadcast; addressing is the + * Messenger's job. + */ +function createBus() { + const listeners = new Set<(event: Frame) => void>(); + const layer: CommunicationLayerMethods = { + sendMessage: async (message) => + listeners.forEach((l) => l(message as Frame)), + addListener: (cb) => listeners.add(cb as (event: Frame) => void), + removeListener: (cb) => listeners.delete(cb as (event: Frame) => void), + }; + return layer; +} + +/** + * Stand up a real DevtoolsPlugin for a player, wired to a real Messenger + * (id = playerID) on the shared bus — exactly as the platform layer does. The + * handler's processInteraction is a spy so we can assert which player actually + * handled an action. + */ +function createPlayer(playerID: string, layer: CommunicationLayerMethods) { + const processInteraction = vi.fn(); + const handler: DevtoolsHandler = { + processInteraction, + checkIfDevtoolsIsActive: () => true, + }; + + const corePlugin = new DevtoolsPlugin({ + playerID, + pluginData: pluginData(), + handler, + }); + + const options: MessengerOptions = { + id: playerID, + context: "player", + messageCallback: (message) => + corePlugin.store.dispatch( + message as Parameters[0], + ), + ...layer, + logger: console, + }; + const messenger = new Messenger(options); + corePlugin.registerMessenger(messenger); + + // Announce this player to the devtools side so the MCP client knows it + // exists (handleInvokeAction validates the player is known). + corePlugin["dispatchPlayerInit"](); + + return { processInteraction, corePlugin, messenger }; +} + +describe("invoke_action end-to-end routing", () => { + beforeEach(() => { + vi.clearAllMocks(); + Messenger.reset(); + }); + + function setup() { + const layer = createBus(); + const playerA = createPlayer("player-a", layer); + const playerB = createPlayer("player-b", layer); + + // The devtools/MCP-side Messenger. This is the same Messenger that + // `createExtensionClient` builds internally; we drive it directly to avoid + // the UI-coupled client barrel. + const devtools = new Messenger({ + id: "devtools", + context: "devtools", + messageCallback: () => {}, + ...layer, + logger: console, + }); + + // Let beacons/handshakes settle so connections are established both ways. + vi.advanceTimersByTime(2000); + + /** + * Emit the exact frame `client.handleInteraction({ type, target })` sends + * for an `invoke_action` resolved to `target`. + */ + const invokeOn = (target: string) => + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "next" }, + target, + } as unknown as ExtensionSupportedEvents); + + /** + * Emit the broadcast `player-selected` interaction that `client.selectPlayer` + * sends (no target — goes to every player). + */ + const selectPlayer = (playerID: string) => + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "player-selected", payload: playerID }, + } as unknown as ExtensionSupportedEvents); + + return { devtools, invokeOn, selectPlayer, playerA, playerB }; + } + + it("delivers an action only to the targeted player", () => { + const { invokeOn, playerA, playerB } = setup(); + + invokeOn("player-b"); + vi.advanceTimersByTime(1000); + + expect(playerB.processInteraction).toHaveBeenCalledWith( + expect.objectContaining({ + payload: expect.objectContaining({ type: "next" }), + }), + ); + expect(playerA.processInteraction).not.toHaveBeenCalled(); + }); + + it("delivers to the other player when it is the target (mirror)", () => { + const { invokeOn, playerA, playerB } = setup(); + + invokeOn("player-a"); + vi.advanceTimersByTime(1000); + + expect(playerA.processInteraction).toHaveBeenCalledWith( + expect.objectContaining({ + payload: expect.objectContaining({ type: "next" }), + }), + ); + expect(playerB.processInteraction).not.toHaveBeenCalled(); + }); + + // Regression for the original failing scenario: select a player (a broadcast + // interaction), then invoke an action on that same player. Before the + // messenger sequencing fixes, the broadcast advanced the receiver's counter + // and the follow-up action was dropped as a duplicate. The selected player + // must receive BOTH, and the other player only the broadcast select. + it("delivers a follow-up action after selecting the same player", () => { + const { invokeOn, selectPlayer, playerA, playerB } = setup(); + + selectPlayer("player-a"); + invokeOn("player-a"); + vi.advanceTimersByTime(1000); + + const aTypes = playerA.processInteraction.mock.calls.map( + (c) => (c[0] as { payload?: { type?: string } })?.payload?.type, + ); + expect(aTypes).toContain("player-selected"); + expect(aTypes).toContain("next"); + // player-selected handled exactly once (no re-entrant double-fire) + expect(aTypes.filter((t) => t === "player-selected")).toHaveLength(1); + + // player-b saw only the broadcast select, never the targeted action + const bTypes = playerB.processInteraction.mock.calls.map( + (c) => (c[0] as { payload?: { type?: string } })?.payload?.type, + ); + expect(bTypes).not.toContain("next"); + }); + + // The MCP handler's playerId→target resolution (including the "no playerId → + // currently selected player" fallback) is unit-tested in select.test.ts. +}); diff --git a/devtools/mcp/src/tools/__tests__/select.test.ts b/devtools/mcp/src/tools/__tests__/select.test.ts new file mode 100644 index 0000000..f3d38ad --- /dev/null +++ b/devtools/mcp/src/tools/__tests__/select.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect, vi } from "vitest"; +import type { ExtensionClient } from "@player-devtools/client"; +import type { ExtensionState } from "@player-devtools/types"; + +import { handleInvokeAction } from "../select"; + +type Interaction = { type: string; payload?: string; target?: string }; + +/** + * Build a fake ExtensionClient seeded with two players, the first selected. + * `handleInteraction` is a spy so we can assert which player a message is + * addressed to. + */ +function setup(selected = "player-a") { + const handleInteraction = vi.fn<(interaction: Interaction) => void>(); + + const players = { + "player-a": { + active: true, + plugins: { pluginX: { id: "pluginX" } }, + }, + "player-b": { + active: true, + plugins: { pluginX: { id: "pluginX" } }, + }, + }; + + const client = { + getState: () => + ({ + players, + current: { player: selected }, + }) as unknown as ExtensionState, + handleInteraction, + } as unknown as ExtensionClient; + + return { client, handleInteraction }; +} + +describe("handleInvokeAction", () => { + it("addresses the message to the currently selected player when no playerId is given", () => { + const { client, handleInteraction } = setup("player-a"); + + handleInvokeAction(client, { pluginId: "pluginX", action: "next" }); + + expect(handleInteraction).toHaveBeenCalledWith( + expect.objectContaining({ type: "next", target: "player-a" }), + ); + }); + + // Negative/regression test: an explicit playerId that differs from the + // selected player MUST be the target. The previous implementation validated + // against the explicit id but then dropped it, letting handleInteraction + // re-derive the target from current.player — so the message was sent to the + // selected player instead. This asserts the message is NOT misrouted. + it("addresses the message to the explicit playerId, not the selected player", () => { + const { client, handleInteraction } = setup("player-a"); + + handleInvokeAction(client, { + playerId: "player-b", + pluginId: "pluginX", + action: "next", + }); + + const interaction = handleInteraction.mock.calls[0]?.[0]; + expect(interaction?.target).toBe("player-b"); + expect(interaction?.target).not.toBe("player-a"); + }); +}); diff --git a/devtools/mcp/src/tools/flow.ts b/devtools/mcp/src/tools/flow.ts index 9c4f246..029cb98 100644 --- a/devtools/mcp/src/tools/flow.ts +++ b/devtools/mcp/src/tools/flow.ts @@ -1,8 +1,10 @@ import type { ExtensionClient } from "@player-devtools/client"; import type { PluginData } from "@player-devtools/types"; -import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; +import type { ToolDef } from "./index"; + type Failure = { error: string }; type PlayerOk = { id: string; @@ -14,75 +16,15 @@ type PlayerOk = { }; type BasicPluginOk = PlayerOk & { basicPlugin: PluginData }; -const PlayerInput = z.object({ playerId: z.string().optional() }); - -export const getFlowTool: Tool = { - name: "get_flow", - description: - "Get the current flow from the basic devtools plugin for a Player instance.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - }, - required: [], - }, -}; - -export const getDataTool: Tool = { - name: "get_data", - description: - "Get the current flow data model from the basic devtools plugin for a Player instance.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - }, - required: [], - }, -}; - -export const getLogsTool: Tool = { - name: "get_logs", - description: - "Get the logs from the basic devtools plugin for a Player instance.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - }, - required: [], - }, +/** Optional player id, shared by the player-scoped read tools. */ +const playerIdShape = { + playerId: z + .string() + .optional() + .describe("Player ID. Defaults to the currently selected player."), }; -export const getPluginDataTool: Tool = { - name: "get_plugin_data", - description: "Get a specific data key from any plugin for a Player instance.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - pluginId: { type: "string", description: "Plugin ID." }, - dataKey: { - type: "string", - description: "Key to retrieve from the plugin's data.", - }, - }, - required: ["pluginId", "dataKey"], - }, -}; +const PlayerInput = z.object(playerIdShape); function resolvePlayer( client: ExtensionClient, @@ -152,11 +94,13 @@ export function handleGetLogs( ); } -const GetPluginDataInput = z.object({ - playerId: z.string().optional(), - pluginId: z.string(), - dataKey: z.string(), -}); +const getPluginDataShape = { + ...playerIdShape, + pluginId: z.string().describe("Plugin ID."), + dataKey: z.string().describe("Key to retrieve from the plugin's data."), +}; + +const GetPluginDataInput = z.object(getPluginDataShape); export function handleGetPluginData( client: ExtensionClient, @@ -172,3 +116,34 @@ export function handleGetPluginData( ]; return ok(value ?? null); } + +export const getFlowDef: ToolDef = { + name: "get_flow", + description: + "Get the current flow from the basic devtools plugin for a Player instance.", + inputSchema: playerIdShape, + handle: handleGetFlow, +}; + +export const getDataDef: ToolDef = { + name: "get_data", + description: + "Get the current flow data model from the basic devtools plugin for a Player instance.", + inputSchema: playerIdShape, + handle: handleGetData, +}; + +export const getLogsDef: ToolDef = { + name: "get_logs", + description: + "Get the logs from the basic devtools plugin for a Player instance.", + inputSchema: playerIdShape, + handle: handleGetLogs, +}; + +export const getPluginDataDef: ToolDef = { + name: "get_plugin_data", + description: "Get a specific data key from any plugin for a Player instance.", + inputSchema: getPluginDataShape, + handle: handleGetPluginData, +}; diff --git a/devtools/mcp/src/tools/index.ts b/devtools/mcp/src/tools/index.ts index adc1b96..c4a1056 100644 --- a/devtools/mcp/src/tools/index.ts +++ b/devtools/mcp/src/tools/index.ts @@ -1,4 +1,39 @@ +import type { ExtensionClient } from "@player-devtools/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { ZodRawShape } from "zod"; + +/** + * A tool definition consumed by the MCP server. `inputSchema` is a Zod raw + * shape — the SDK derives the JSON Schema, validates input, and types the + * handler's `args`. The handler receives the running devtools client plus the + * validated args. + */ +export type ToolDef = { + name: string; + description: string; + inputSchema: Shape; + handle: (client: ExtensionClient, args: unknown) => CallToolResult; +}; + export * from "./flow"; export * from "./players"; export * from "./plugins"; export * from "./select"; + +import { listPlayersDef, getPlayerStatusDef } from "./players"; +import { getFlowDef, getDataDef, getLogsDef, getPluginDataDef } from "./flow"; +import { describePluginDef } from "./plugins"; +import { selectPlayerDef, invokeActionDef } from "./select"; + +/** Every tool the MCP server exposes. */ +export const TOOL_DEFS: ToolDef[] = [ + listPlayersDef, + getPlayerStatusDef, + getFlowDef, + getDataDef, + getLogsDef, + getPluginDataDef, + describePluginDef, + selectPlayerDef, + invokeActionDef, +]; diff --git a/devtools/mcp/src/tools/players.ts b/devtools/mcp/src/tools/players.ts index d45e1a0..47aa7d9 100644 --- a/devtools/mcp/src/tools/players.ts +++ b/devtools/mcp/src/tools/players.ts @@ -1,31 +1,17 @@ import type { ExtensionClient } from "@player-devtools/client"; -import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; -export const listPlayersTool: Tool = { - name: "list_players", - description: - "List all Player instances known to the devtools, and which one is currently selected.", - inputSchema: { type: "object", properties: {}, required: [] }, -}; +import type { ToolDef } from "./index"; -export const getPlayerStatusTool: Tool = { - name: "get_player_status", - description: - "Get the active status and registered plugin IDs for a Player instance.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - }, - required: [], - }, +const playerIdShape = { + playerId: z + .string() + .optional() + .describe("Player ID. Defaults to the currently selected player."), }; -const GetPlayerStatusInput = z.object({ playerId: z.string().optional() }); +const GetPlayerStatusInput = z.object(playerIdShape); export function handleListPlayers(client: ExtensionClient): CallToolResult { const { players, current } = client.getState(); @@ -70,3 +56,19 @@ function errorResult(message: string): CallToolResult { content: [{ type: "text", text: JSON.stringify({ error: message }) }], }; } + +export const listPlayersDef: ToolDef = { + name: "list_players", + description: + "List all Player instances known to the devtools, and which one is currently selected.", + inputSchema: {}, + handle: (client) => handleListPlayers(client), +}; + +export const getPlayerStatusDef: ToolDef = { + name: "get_player_status", + description: + "Get the active status and registered plugin IDs for a Player instance.", + inputSchema: playerIdShape, + handle: handleGetPlayerStatus, +}; diff --git a/devtools/mcp/src/tools/plugins.ts b/devtools/mcp/src/tools/plugins.ts index 4baa104..2caf97b 100644 --- a/devtools/mcp/src/tools/plugins.ts +++ b/devtools/mcp/src/tools/plugins.ts @@ -1,28 +1,18 @@ import type { ExtensionClient } from "@player-devtools/client"; -import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; -export const describePluginTool: Tool = { - name: "describe_plugin", - description: - "Get the capability descriptor declared by a plugin at registration time. Use this to discover what data keys and actions the plugin exposes.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - pluginId: { type: "string", description: "Plugin ID." }, - }, - required: ["pluginId"], - }, +import type { ToolDef } from "./index"; + +const describePluginShape = { + playerId: z + .string() + .optional() + .describe("Player ID. Defaults to the currently selected player."), + pluginId: z.string().describe("Plugin ID."), }; -const DescribePluginInput = z.object({ - playerId: z.string().optional(), - pluginId: z.string(), -}); +const DescribePluginInput = z.object(describePluginShape); function err(message: string): CallToolResult { return { @@ -50,3 +40,11 @@ export function handleDescribePlugin( return err(`plugin "${pluginId}" has no capabilities declared`); return ok(plugin.capabilities); } + +export const describePluginDef: ToolDef = { + name: "describe_plugin", + description: + "Get the capability descriptor declared by a plugin at registration time. Use this to discover what data keys and actions the plugin exposes.", + inputSchema: describePluginShape, + handle: handleDescribePlugin, +}; diff --git a/devtools/mcp/src/tools/select.ts b/devtools/mcp/src/tools/select.ts index 27f3d4b..7fa1522 100644 --- a/devtools/mcp/src/tools/select.ts +++ b/devtools/mcp/src/tools/select.ts @@ -1,47 +1,26 @@ import type { ExtensionClient } from "@player-devtools/client"; -import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; -export const selectPlayerTool: Tool = { - name: "select_player", - description: - "Select a Player instance as the active target for subsequent tool calls.", - inputSchema: { - type: "object", - properties: { - playerId: { type: "string", description: "The Player ID to select." }, - }, - required: ["playerId"], - }, +import type { ToolDef } from "./index"; + +const selectPlayerShape = { + playerId: z.string().describe("The Player ID to select."), }; -export const invokeActionTool: Tool = { - name: "invoke_action", - description: - "Invoke a named action on a plugin. Use describe_plugin first to discover available actions.", - inputSchema: { - type: "object", - properties: { - playerId: { - type: "string", - description: "Player ID. Defaults to the currently selected player.", - }, - pluginId: { type: "string", description: "Plugin ID." }, - action: { type: "string", description: "Action name." }, - payload: { type: "string", description: "Optional stringified payload." }, - }, - required: ["pluginId", "action"], - }, +const invokeActionShape = { + playerId: z + .string() + .optional() + .describe("Player ID. Defaults to the currently selected player."), + pluginId: z.string().describe("Plugin ID."), + action: z.string().describe("Action name."), + payload: z.string().optional().describe("Optional stringified payload."), }; -const SelectPlayerInput = z.object({ playerId: z.string() }); +const SelectPlayerInput = z.object(selectPlayerShape); -const InvokeActionInput = z.object({ - playerId: z.string().optional(), - pluginId: z.string(), - action: z.string(), - payload: z.string().optional(), -}); +const InvokeActionInput = z.object(invokeActionShape); function err(message: string): CallToolResult { return { @@ -83,6 +62,22 @@ export function handleInvokeAction( ) { return err(`action "${action}" not declared in plugin capabilities`); } - client.handleInteraction({ type: action, payload }); + client.handleInteraction({ type: action, payload, target: id }); return ok({ invoked: action }); } + +export const selectPlayerDef: ToolDef = { + name: "select_player", + description: + "Select a Player instance as the active target for subsequent tool calls.", + inputSchema: selectPlayerShape, + handle: handleSelectPlayer, +}; + +export const invokeActionDef: ToolDef = { + name: "invoke_action", + description: + "Invoke a named action on a plugin. Use describe_plugin first to discover available actions.", + inputSchema: invokeActionShape, + handle: handleInvokeAction, +}; diff --git a/devtools/mcp/src/transport.ts b/devtools/mcp/src/transport.ts index 7638fb9..ed43f2d 100644 --- a/devtools/mcp/src/transport.ts +++ b/devtools/mcp/src/transport.ts @@ -3,9 +3,11 @@ import { FlipperServerState, type FlipperServer, } from "flipper-server-client"; -import { WebSocketServer, type WebSocket } from "ws"; -import { spawn, type ChildProcess } from "child_process"; +import { spawn } from "child_process"; import * as net from "net"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; import type { CommunicationLayerMethods, ExtensionSupportedEvents, @@ -70,9 +72,132 @@ function waitForPort( }); } +/** + * Cross-process refcount for the shared `flipper-server` daemon. + * + * Many MCP server processes (one per project/user registration) attach to a + * single `flipper-server` on a fixed port. No in-process variable can + * coordinate their lifecycles, so we track liveness in a small file guarded by + * an atomic lock directory: + * + * - The first attach starts the daemon and records its PID with `refs: 1`. + * - Each subsequent attach increments `refs`. + * - Each detach decrements `refs`; the last one out shuts the daemon down. + * + * The lock directory (`mkdir` is atomic across processes) serializes the + * read-modify-write so concurrently-starting instances don't race. + */ +type RefcountFile = { pid: number; refs: number }; + +class FlipperRefcount { + private readonly dir = path.join(os.tmpdir(), "player-devtools-mcp"); + private readonly file = path.join(this.dir, "flipper-server.refcount"); + private readonly lock = path.join(this.dir, "flipper-server.lock"); + + /** Acquire the cross-process lock, run `fn`, then release — even on throw. */ + private withLock(fn: () => T): T { + fs.mkdirSync(this.dir, { recursive: true }); + const deadline = Date.now() + 5_000; + // Spin on an atomic mkdir until we own the lock or time out. + for (;;) { + try { + fs.mkdirSync(this.lock); + break; + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err; + if (Date.now() >= deadline) { + // Stale lock from a crashed process — reclaim it. + try { + fs.rmdirSync(this.lock); + } catch { + /* another instance won the reclaim; retry */ + } + } + } + } + try { + return fn(); + } finally { + try { + fs.rmdirSync(this.lock); + } catch { + /* already gone */ + } + } + } + + private read(): RefcountFile | null { + try { + return JSON.parse(fs.readFileSync(this.file, "utf8")) as RefcountFile; + } catch { + return null; + } + } + + private write(state: RefcountFile): void { + fs.writeFileSync(this.file, JSON.stringify(state)); + } + + private clear(): void { + try { + fs.unlinkSync(this.file); + } catch { + /* already gone */ + } + } + + /** Is the recorded daemon PID still alive? */ + private isAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + } + + /** + * Register interest in the daemon. Returns whether this caller is the one + * responsible for starting it (because no live daemon was recorded). + * `recordPid` is called with the started PID once the daemon is up. + */ + acquire(): { shouldStart: boolean; commit: (pid: number) => void } { + return this.withLock(() => { + const state = this.read(); + if (state && this.isAlive(state.pid)) { + this.write({ pid: state.pid, refs: state.refs + 1 }); + return { shouldStart: false, commit: () => {} }; + } + // No live daemon — this caller will start one and record its PID. + return { + shouldStart: true, + commit: (pid: number) => + this.withLock(() => this.write({ pid, refs: 1 })), + }; + }); + } + + /** + * Drop this caller's interest. Returns the PID to kill if this was the last + * reference, otherwise null. + */ + release(): number | null { + return this.withLock(() => { + const state = this.read(); + if (!state) return null; + if (state.refs <= 1) { + this.clear(); + return state.pid; + } + this.write({ pid: state.pid, refs: state.refs - 1 }); + return null; + }); + } +} + export class FlipperServerTransport implements Transport { private server: FlipperServer | null = null; - private flipperProcess: ChildProcess | null = null; + private refcount = new FlipperRefcount(); private listeners = new Set(); /** @@ -94,42 +219,36 @@ export class FlipperServerTransport implements Transport { const host = this.options.host ?? "localhost"; const port = this.options.port ?? 52342; - // Check if flipper-server is already listening; if not, start it - const alreadyRunning = await new Promise((resolve) => { - const socket = net.connect(port, host); - socket.once("connect", () => { - socket.destroy(); - resolve(true); - }); - socket.once("error", () => { - socket.destroy(); - resolve(false); - }); - }); + // Register interest in the shared daemon. The first instance to do so is + // told to start it; the rest just attach. The daemon outlives any single + // MCP process and is only torn down when the last instance detaches. + const { shouldStart, commit } = this.refcount.acquire(); - if (!alreadyRunning) { + if (shouldStart) { console.log("[FlipperServerTransport] Starting flipper-server..."); const serverScript = require.resolve("flipper-server/server.js"); - this.flipperProcess = spawn( - process.execPath, - [serverScript, "--open=true"], - { - stdio: "inherit", - detached: false, - }, - ); - this.flipperProcess.on("error", (err) => { + // Detached + unref'd: the daemon must survive this process exiting so + // other instances keep their connections. We never kill it directly — + // shutdown is driven by the refcount in close(). + const child = spawn(process.execPath, [serverScript, "--open=true"], { + stdio: "inherit", + detached: true, + }); + child.on("error", (err: Error) => { console.error( "[FlipperServerTransport] flipper-server process error:", err, ); }); - // Kill the child synchronously on any form of exit so it doesn't orphan - process.on("exit", () => this.flipperProcess?.kill()); + child.unref(); await waitForPort(host, port); + commit(child.pid!); console.log("[FlipperServerTransport] flipper-server ready."); } else { - console.log("[FlipperServerTransport] flipper-server already running."); + // Daemon already running (started by another instance) — wait for it to + // accept connections in case it's still coming up, then attach. + await waitForPort(host, port); + console.log("[FlipperServerTransport] Attached to flipper-server."); } // Read the auth token the flipper-server wrote during startup @@ -245,103 +364,18 @@ export class FlipperServerTransport implements Transport { this.activeClientIds.clear(); this.server?.close(); this.server = null; - if (this.flipperProcess) { - this.flipperProcess.kill(); - this.flipperProcess = null; - } - } -} - -/** Default port the MCP server listens on for WebSocket connections */ -export const DEFAULT_WS_PORT = 7382; - -/** - * WebSocket server transport - * - * The MCP server opens a WebSocket server; the player connects as a client - * (via `useWSCommunicationLayer`). Works for: - * - Browser-based players - * - iOS/Android simulators over localhost - * - Physical devices over WiFi (same LAN) - */ -export class WebSocketServerTransport implements Transport { - private wss: WebSocketServer | null = null; - private clients = new Set(); - private listeners = new Set(); - - constructor( - private options: { - /** Port to listen on; defaults to 7382 */ - port?: number; - /** Host to bind to; defaults to "localhost" */ - host?: string; - } = {}, - ) {} - - async connect(): Promise { - const port = this.options.port ?? DEFAULT_WS_PORT; - const host = this.options.host ?? "localhost"; - - await new Promise((resolve, reject) => { - this.wss = new WebSocketServer({ port, host }); - - this.wss.on("listening", resolve); - this.wss.on("error", reject); - - this.wss.on("connection", (socket) => { - this.clients.add(socket); - socket.on("message", (data) => { - let parsed: TransactionMetadata & - MessengerEvent; - try { - parsed = JSON.parse(data.toString()); - } catch { - return; - } - for (const listener of this.listeners) { - listener(parsed); - } - }); - - socket.on("close", () => { - this.clients.delete(socket); - }); - - socket.on("error", (err) => { - console.warn("[WebSocketServerTransport] Client error:", err); - this.clients.delete(socket); - }); - }); - }); - - console.log(`[WebSocketServerTransport] Listening on ws://${host}:${port}`); - } - - sendMessage: CommunicationLayerMethods["sendMessage"] = async (message) => { - const data = JSON.stringify(message); - for (const client of this.clients) { - if (client.readyState === 1 /* OPEN */) { - client.send(data); + // Drop our reference to the shared daemon. If we were the last user, the + // refcount hands back its PID and we shut it down; otherwise it keeps + // running for the remaining instances. + const pidToKill = this.refcount.release(); + if (pidToKill !== null) { + try { + process.kill(pidToKill); + console.log("[FlipperServerTransport] Shut down flipper-server."); + } catch { + /* already gone */ } } - }; - - addListener: CommunicationLayerMethods["addListener"] = (callback) => { - this.listeners.add(callback); - }; - - removeListener: CommunicationLayerMethods["removeListener"] = (callback) => { - this.listeners.delete(callback); - }; - - async close(): Promise { - for (const client of this.clients) { - client.close(); - } - this.clients.clear(); - this.listeners.clear(); - await new Promise((resolve) => this.wss?.close(() => resolve())); - this.wss = null; } } diff --git a/devtools/messenger/core/src/__tests__/index.test.ts b/devtools/messenger/core/src/__tests__/index.test.ts index 4d04700..0b7ebea 100644 --- a/devtools/messenger/core/src/__tests__/index.test.ts +++ b/devtools/messenger/core/src/__tests__/index.test.ts @@ -1,7 +1,11 @@ -import { describe, beforeEach, expect, test, vi } from "vitest"; +import { describe, beforeEach, expect, test, vi, type Mock } from "vitest"; import { Messenger } from "../index"; import { createMockContext } from "./helpers"; -import { BaseEvent } from "@player-devtools/types"; +import { + BaseEvent, + type Transaction, + type ExtensionSupportedEvents, +} from "@player-devtools/types"; vi.useFakeTimers(); @@ -368,4 +372,203 @@ describe("Messenger", () => { expect.objectContaining({ type: "MESSENGER_BEACON" }), ); }); + + // The MCP server addresses outbound actions to a specific player via the + // `target` field; on the device, each Player runs a Messenger whose `id` is + // its playerID. This proves the receiving side actually drops messages whose + // `target` isn't its own id — i.e. a Player only handles actions meant for + // it, even though every Player sees every message on the shared bus. + describe("target-based routing", () => { + /** + * Wire N player messengers onto one shared bus. Each gets a + * messageCallback spy; a message is "handled" by a player iff its callback + * fires. + */ + function bus(...ids: string[]) { + type Frame = Transaction; + const listeners = new Set<(event: Frame) => void>(); + const handled: Record = {}; + const messengers: Array> = []; + + const api = { + sendMessage: async (event: Frame) => + listeners.forEach((listener) => listener(event)), + addListener: (cb: (event: Frame) => void) => { + listeners.add(cb); + }, + removeListener: (cb: (event: Frame) => void) => { + listeners.delete(cb); + }, + }; + + for (const id of ids) { + const messageCallback = vi.fn(); + handled[id] = messageCallback; + const messenger = new Messenger({ + ...api, + messageCallback, + context: "player", + id, + logger: console, + }); + messengers.push(messenger); + } + + /** Inject a frame as if it arrived from the devtools/MCP side. */ + const inject = (target: string | undefined) => + api.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "next" }, + context: "devtools", + sender: "devtools-mcp", + _messenger_: true, + ...(target ? { target } : {}), + } as unknown as Frame); + + return { handled, inject, messengers }; + } + + test("only the targeted player handles the interaction", () => { + const { handled, inject } = bus("player-a", "player-b"); + + inject("player-b"); + + expect(handled["player-b"]).toHaveBeenCalledWith( + expect.objectContaining({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + target: "player-b", + }), + ); + expect(handled["player-a"]).not.toHaveBeenCalled(); + }); + + test("an untargeted message is handled by every player", () => { + const { handled, inject } = bus("player-a", "player-b"); + + inject(undefined); + + expect(handled["player-a"]).toHaveBeenCalled(); + expect(handled["player-b"]).toHaveBeenCalled(); + }); + + // Regression: a broadcast (no target, id === -1) followed by a targeted + // message to the SAME player must still deliver the targeted message. The + // broadcast previously advanced the receiver's `messagesReceived`, so the + // targeted message's id looked like an already-seen duplicate and was + // dropped. Drives the real sender `sendMessage` so transaction ids are + // assigned exactly as in production. + test("a targeted message after a broadcast is still delivered (not seen as duplicate)", () => { + type Frame = Transaction; + const listeners = new Set<(event: Frame) => void>(); + const layer = { + sendMessage: async (event: Frame) => listeners.forEach((l) => l(event)), + addListener: (cb: (event: Frame) => void) => { + listeners.add(cb); + }, + removeListener: (cb: (event: Frame) => void) => { + listeners.delete(cb); + }, + }; + + const received = vi.fn(); + const player = new Messenger({ + ...layer, + messageCallback: received, + context: "player", + id: "player-a", + logger: console, + }); + const devtools = new Messenger({ + ...layer, + messageCallback: () => {}, + context: "devtools", + id: "devtools", + logger: console, + }); + // establish the connection both ways + vi.advanceTimersByTime(2000); + received.mockClear(); + + // broadcast (no target) — reaches player-a but must not advance its + // sequence counter for the devtools connection + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "player-selected", payload: "player-a" }, + } as unknown as ExtensionSupportedEvents); + + // targeted follow-up to the same player + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "next" }, + target: "player-a", + } as unknown as ExtensionSupportedEvents); + + const delivered = received.mock.calls + .map((c) => (c[0] as Frame).payload as { type?: string }) + .map((p) => p?.type); + expect(delivered).toContain("player-selected"); + expect(delivered).toContain("next"); + + // keep a reference so the messengers aren't flagged unused + expect(player).toBeDefined(); + }); + + // Regression: two consecutive TARGETED messages to the same player must + // both arrive in sequence. `sendMessage` used to increment `messagesSent` + // a second time (on top of `getTransactionID`), so the second message was + // stamped id=3 instead of 2 — a gap past the receiver's messagesReceived + // that triggered lost-event recovery instead of delivery. + test("consecutive targeted messages keep contiguous ids and both deliver", () => { + type Frame = Transaction; + const listeners = new Set<(event: Frame) => void>(); + const layer = { + sendMessage: async (event: Frame) => listeners.forEach((l) => l(event)), + addListener: (cb: (event: Frame) => void) => { + listeners.add(cb); + }, + removeListener: (cb: (event: Frame) => void) => { + listeners.delete(cb); + }, + }; + + const received = vi.fn(); + const player = new Messenger({ + ...layer, + messageCallback: received, + context: "player", + id: "player-a", + logger: console, + }); + const devtools = new Messenger({ + ...layer, + messageCallback: () => {}, + context: "devtools", + id: "devtools", + logger: console, + }); + vi.advanceTimersByTime(2000); + received.mockClear(); + + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "first" }, + target: "player-a", + } as unknown as ExtensionSupportedEvents); + devtools.sendMessage({ + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type: "second" }, + target: "player-a", + } as unknown as ExtensionSupportedEvents); + + const ids = received.mock.calls.map((c) => (c[0] as Frame).id); + const delivered = received.mock.calls.map( + (c) => ((c[0] as Frame).payload as { type?: string })?.type, + ); + expect(delivered).toContain("first"); + expect(delivered).toContain("second"); + // contiguous ids 1, 2 — no gap that would trip lost-event recovery + expect(ids).toEqual([1, 2]); + expect(player).toBeDefined(); + }); + }); }); diff --git a/devtools/messenger/core/src/index.ts b/devtools/messenger/core/src/index.ts index bc4320d..2b4208d 100644 --- a/devtools/messenger/core/src/index.ts +++ b/devtools/messenger/core/src/index.ts @@ -268,7 +268,11 @@ export class Messenger> { connection.messagesReceived += ( parsed.payload as EventsBatchEvent["payload"] ).events.length; - } else { + } else if (transactionID > -1) { + // Only sequenced messages advance the counter. Broadcasts (id === -1, + // no target) are delivered to everyone but were never sequenced, so + // counting them would make the next targeted message look like a + // duplicate (its id <= the inflated messagesReceived) and be dropped. connection.messagesReceived += 1; } } @@ -367,12 +371,11 @@ export class Messenger> { this.addEvent(parsed); const target = parsed.target || null; + // `addTransactionMetadata` → `getTransactionID` already increments the + // target connection's `messagesSent` and stamps that value as the id. + // Do NOT increment again here, or the sender's counter desyncs from the + // stamped ids (corrupting duplicate detection and lost-event replay). const msg = this.addTransactionMetadata(parsed); - const connection = target ? this.getConnection(target) : null; - - if (connection) { - connection.messagesSent += 1; - } return this.options.sendMessage(msg).catch(() => { this.options.handleFailedMessage?.(msg); diff --git a/devtools/plugin/core/src/__tests__/plugin.test.ts b/devtools/plugin/core/src/__tests__/plugin.test.ts new file mode 100644 index 0000000..a29cbe6 --- /dev/null +++ b/devtools/plugin/core/src/__tests__/plugin.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, vi } from "vitest"; +import type { + DevtoolsPluginInteractionEvent, + ExtensionSupportedEvents, + PluginData, + Transaction, +} from "@player-devtools/types"; + +import { DevtoolsPlugin, type DevtoolsHandler } from "../plugin"; + +const PLUGIN_ID = "test-plugin"; + +function pluginData(): PluginData { + return { + id: PLUGIN_ID, + version: "1.0.0", + name: "Test Plugin", + description: "test", + flow: { + id: "flow", + views: [], + navigation: {}, + } as unknown as PluginData["flow"], + }; +} + +function setup(playerID = "player-a") { + const processInteraction = vi.fn(); + const handler: DevtoolsHandler = { + processInteraction, + checkIfDevtoolsIsActive: () => true, + }; + const plugin = new DevtoolsPlugin({ + playerID, + pluginData: pluginData(), + handler, + }); + return { plugin, processInteraction }; +} + +/** Build an inbound interaction transaction as the messenger would deliver it. */ +function interaction( + type: string, + payload?: string, +): Transaction { + const event: DevtoolsPluginInteractionEvent = { + type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION", + payload: { type, payload }, + }; + return { + ...event, + id: -1, + sender: "devtools", + context: "devtools", + target: "player-a", + timestamp: 0, + _messenger_: true, + } as Transaction; +} + +describe("DevtoolsPlugin.processInteraction", () => { + it("processes a plain interaction exactly once", () => { + const { plugin, processInteraction } = setup(); + + plugin.store.dispatch(interaction("next")); + + expect(processInteraction).toHaveBeenCalledTimes(1); + }); + + // Regression: `player-selected` re-enters the store subscriber because it + // synchronously dispatches SELECTED_PLAYER_CHANGE. Previously the interaction + // cursor advanced AFTER that dispatch, so the same interaction was processed + // a second time on re-entry. + it("processes a player-selected interaction exactly once (no re-entrant double-fire)", () => { + const { plugin, processInteraction } = setup(); + + plugin.store.dispatch(interaction("player-selected", "player-a")); + + expect(processInteraction).toHaveBeenCalledTimes(1); + }); + + it("processes a following interaction after a player-selected (cursor not over-advanced)", () => { + const { plugin, processInteraction } = setup(); + + plugin.store.dispatch(interaction("player-selected", "player-a")); + plugin.store.dispatch(interaction("next")); + + expect(processInteraction).toHaveBeenCalledTimes(2); + expect(processInteraction).toHaveBeenLastCalledWith( + expect.objectContaining({ + payload: expect.objectContaining({ type: "next" }), + }), + ); + }); +}); diff --git a/devtools/plugin/core/src/plugin.ts b/devtools/plugin/core/src/plugin.ts index 7042cbf..dc47bc4 100644 --- a/devtools/plugin/core/src/plugin.ts +++ b/devtools/plugin/core/src/plugin.ts @@ -65,9 +65,16 @@ export class DevtoolsPlugin implements PlayerPlugin, DevtoolsHandler { constructor(protected options: DevtoolsPluginOptions) { this.store.subscribe(({ interactions }) => { - if (this.lastProcessedInteraction < (interactions.length ?? 0)) { + const start = this.lastProcessedInteraction; + const end = interactions.length ?? 0; + if (start < end) { + // Advance the cursor BEFORE processing: `processInteraction` may + // dispatch synchronously (e.g. SELECTED_PLAYER_CHANGE), which re-enters + // this subscriber. Advancing first makes that re-entrant pass a no-op + // instead of reprocessing the same interactions. + this.lastProcessedInteraction = end; interactions - .slice(this.lastProcessedInteraction) + .slice(start, end) .forEach(this.processInteraction.bind(this)); } }); @@ -168,8 +175,6 @@ export class DevtoolsPlugin implements PlayerPlugin, DevtoolsHandler { _messenger_: true, }); } - - this.lastProcessedInteraction += 1; } apply(player: Player): void { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3fe20e..2665546 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -424,6 +424,10 @@ importers: '@player-devtools/types': specifier: workspace:* version: link:../types/core + devDependencies: + '@player-devtools/plugin': + specifier: workspace:* + version: link:../plugin/core devtools/messenger/core: dependencies: From fb66be8cad7fd10c06242c2429e9c37d7378d38e Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Mon, 29 Jun 2026 05:50:43 -0700 Subject: [PATCH 4/5] Document the devtools packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add or rewrite READMEs across the devtools workspace, source-grounded and consolidated one-per-family (each covers all its platform packages — TS, JVM, Android, iOS, SwiftUI): - New: mcp, flipper-plugin, plugins/basic, types, utils - Rewritten: plugin and messenger families (messenger also fixes a stale API doc — removed a nonexistent `target` ctor option, added the required `logger`, fixed the import path) - Promote the devtools overview to the repo-root README (workspace map + architecture) and drop the nested devtools/README stub - client: drop the broken license badge and dangling external reference, add sibling cross-links Each README leads with installation (npm / Maven / SPM, plus `claude mcp add` for the MCP server) and links to siblings; all cross-links and anchors resolve. --- README.md | 78 ++++++- devtools/README.md | 12 - devtools/client/README.md | 28 ++- devtools/flipper-plugin/README.md | 127 +++++++++++ devtools/mcp/README.md | 138 ++++++++++++ devtools/messenger/README.md | 237 ++++++++++++++++---- devtools/plugin/README.md | 350 ++++++++++++++++++++++++++++-- devtools/plugins/basic/README.md | 301 +++++++++++++++++++++++++ devtools/types/README.md | 175 +++++++++++++++ devtools/utils/README.md | 165 ++++++++++++++ 10 files changed, 1522 insertions(+), 89 deletions(-) delete mode 100644 devtools/README.md create mode 100644 devtools/flipper-plugin/README.md create mode 100644 devtools/mcp/README.md create mode 100644 devtools/plugins/basic/README.md create mode 100644 devtools/types/README.md create mode 100644 devtools/utils/README.md diff --git a/README.md b/README.md index 4f28bee..2e1f5ae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,77 @@ -# Player DevTools +# Player UI Devtools -TODO \ No newline at end of file +Tools for inspecting and debugging live [Player UI](https://player-ui.github.io) +experiences across web, Android, and iOS. Devtools is itself plugin-driven: a +Devtools **plugin** runs inside the Player you want to inspect and publishes its +state; a Devtools **client** renders that state and sends interactions back. + +## Architecture + +Every part of Devtools is wired together by the [messenger](./devtools/messenger) — a +transport-agnostic, lossless protocol. A plugin on the Player side and a client +on the tooling side each run a `Messenger`; the plugin publishes Player state and +the client drives interactions. + +``` + Player (web / Android / iOS) Tooling + ┌──────────────────────────────┐ ┌────────────────────────────┐ + │ Devtools plugin │ │ Devtools client │ + │ (basic, or your own) │◀────▶│ • browser extension │ + │ taps Player hooks │ msgr │ • Flipper plugin (mobile) │ + │ publishes flow/data/logs │ │ • MCP server (agents) │ + └──────────────────────────────┘ └────────────────────────────┘ +``` + +The content a plugin publishes is rendered by the clients as a Player experience +itself, using the [devtools-assets](https://github.com/player-ui/devtools-assets). + +## Packages + +### Foundations + +| Package | Platforms | Description | +| -------------------------- | ------------------ | ------------------------------------------------------- | +| [`messenger`](./devtools/messenger) | TS · JVM · iOS | The communication protocol all of Devtools is built on. | +| [`types`](./devtools/types) | TS · iOS | Shared event, transaction, and state types. | +| [`utils`](./devtools/utils) | TS · iOS · SwiftUI | Shared utilities (e.g. `dsetAssign`). | + +### Plugins (Player side) + +| Package | Platforms | Description | +| ---------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| [`plugin`](./devtools/plugin) | TS · React · Android · JVM · iOS · SwiftUI | Base classes for building Devtools plugins. | +| [`plugins/basic`](./devtools/plugins/basic) | all of the above | The standard plugin — exposes flow, data, logs, config; supports expression evaluation and flow overrides. The reference implementation. | + +### Clients (tooling side) + +| Package | Platform | Description | +| ------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------- | +| [`client`](./devtools/client) | Web/React | The `Panel` component that renders plugin content; embedded by the browser extension and Flipper plugin. | +| [`flipper-plugin`](./devtools/flipper-plugin) | Flipper desktop | Client for inspecting mobile (Android/iOS) Players. | +| [`mcp`](./devtools/mcp) | stdio / MCP | Client that exposes Devtools to AI agents as MCP tools. | + +> The browser-extension client lives in a separate repo: +> [player-ui/browser-devtools](https://github.com/player-ui/browser-devtools). + +## Getting started + +To debug an existing Player, add the [basic plugin](./devtools/plugins/basic) for your +platform and connect a client: + +- **Web** — add `BasicReactDevtoolsPlugin` and activate the connection from the + browser extension popup. +- **Mobile** — add the platform basic plugin and connect Flipper (see + [`just install-flipper-client`](./devtools/flipper-plugin)). +- **Agents** — run the [MCP server](./devtools/mcp) against a running Flipper server. + +To debug capabilities specific to your integration, build your own plugin on top +of the [`plugin`](./devtools/plugin) base classes — the basic plugin is the best +reference. + +## Building + +Devtools is built with [Bazel](https://bazel.build) via +[`rules_player`](https://github.com/player-ui/rules_player); common tasks are +wrapped in the repo `justfile` (e.g. `just install-flipper-client`, `just mcp`). +Each package's BUILD file uses the standard `rules_player` macros (`js_pipeline`, +`kt_jvm`, `kt_android`, `ios_library`, `swiftui_plugin`). diff --git a/devtools/README.md b/devtools/README.md deleted file mode 100644 index 7d98015..0000000 --- a/devtools/README.md +++ /dev/null @@ -1,12 +0,0 @@ -TODO: Potentially restructure modules: -- packages - - client - - browser-client (currently in another repo) - - flipper-client - - messenger - - plugin - - types - - utils -- plugins - - basic - - profiler diff --git a/devtools/client/README.md b/devtools/client/README.md index f8b32cf..7202336 100644 --- a/devtools/client/README.md +++ b/devtools/client/README.md @@ -1,18 +1,8 @@ # @player-devtools/client -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) - The `@player-devtools/client` exposes the Panel with the ReactPlayer, which is responsible for running content sent by Player devtool plugins on the inspected Player UI instance. -You can check how to use it in the [browser-extension](https://github.com/player-ui/browser-devtools) and [Flipper plugin](../flipper-plugin). - -## Overview - -The Devtools client is a part of the Player UI Devtools architecture. It allows you to create custom devtools panels that can be used to debug and inspect your Player UI experiences, using the same plugin system used by other Player UI plugins. - -The Devtools client conveniently receives its content from the devtools plugins running into the Player UI in use by the inspected page. This feature allows you to extend the dev tools with custom panels, without the need to create a new extension. You can create your own devtools plugins and use them in the Player UI Devtools Browser Extension. - -For a more comprehensive understanding of the architecture of the Devtools client, you can always refer to the detailed information provided in the Devtools Browser Extension README. +The `Panel` is the shared devtools UI surface, hosted by each client: the [browser extension](https://github.com/player-ui/browser-devtools) for web and the [Flipper plugin](../flipper-plugin) for mobile. The agent-facing [MCP server](../mcp) consumes the same Player devtools instrumentation without rendering the `Panel`. ## Installation @@ -26,6 +16,14 @@ npm install @player-devtools/client yarn add @player-devtools/client ``` +## Overview + +The Devtools client is a part of the Player UI Devtools architecture. It allows you to create custom devtools panels that can be used to debug and inspect your Player UI experiences, using the same plugin system used by other Player UI plugins. + +The Devtools client conveniently receives its content from the devtools plugins running into the Player UI in use by the inspected page. This feature allows you to extend the dev tools with custom panels, without the need to create a new extension. You can create your own devtools plugins and use them in the Player UI Devtools Browser Extension. + +For a more comprehensive understanding of the architecture of the Devtools client, see the [Devtools root README](../../README.md) for the overall picture and the [plugin authoring guide](../plugin) for how plugins instrument a Player and feed content to this client. + ## Usage The Devtools client is a React component that receives content from devtools plugins running in the Player UI used by the inspected page. It can be used in your React application like any other React component. @@ -57,6 +55,12 @@ const communicationLayer: Pick< root.render(); ``` +## Related + +- Sibling clients that host this `Panel`: the [Flipper plugin](../flipper-plugin) (mobile) and the browser extension (web). +- [`../mcp`](../mcp) — the agent-facing devtools surface. +- [`../README.md`](../../README.md) — the overall Player UI Devtools architecture. + ## Contributing -We welcome contributions to the Player UI Devtools Browser Extension. +We welcome contributions to the Player UI Devtools. diff --git a/devtools/flipper-plugin/README.md b/devtools/flipper-plugin/README.md new file mode 100644 index 0000000..ffda130 --- /dev/null +++ b/devtools/flipper-plugin/README.md @@ -0,0 +1,127 @@ +# Flipper Devtools Plugin + +The **Flipper Devtools Plugin** (`flipper-plugin-player-ui-devtools`) brings the +Player UI Devtools into [Flipper](https://fbflipper.com), Meta's mobile +debugging desktop app. It is the desktop counterpart for inspecting Players +running in a **mobile** app (iOS / Android) — the same role the browser +extension plays for web. + +It is a thin shell: it wraps the shared [`@player-devtools/client`](../client) +`Panel` in a Flipper UI surface and supplies the `Panel` with a +`CommunicationLayerMethods` transport backed by Flipper's client messaging. All +the actual devtools UI and behavior live in the client; this package only +adapts Flipper's plumbing to the [transport contract](../types). + +| Field | Value | +| --- | --- | +| Package | `flipper-plugin-player-ui-devtools` | +| Flipper `id` | `player-ui-devtools` | +| `pluginType` | `client` (a per-app-connection plugin) | +| Title | Player UI Devtools | + +``` +Mobile app (Player + native devtools plugin) + │ Flipper client connection + ▼ +Flipper desktop ──"message::plugin" (inbound)──▶ plugin() ──▶ Panel (@player-devtools/client) + ▲ │ + └──────────── "message::flipper" (outbound) ◀────────────────┘ +``` + +## Installation & usage + +> This is a **Flipper desktop plugin**, not an npm dependency you add to app +> code. You don't `npm install` it into a project — you install it into your +> local Flipper. + +> **PREFERRED: let the MCP server do it for you.** +> The [MCP server](../mcp) starts and manages a `flipper-server` automatically — +> the first MCP process spawns it, the rest attach, and the last one out shuts it +> down (see the [shared daemon](../mcp#shared-flipper-server-daemon) section). +> If you're driving devtools through an agent, you don't need to install or run +> Flipper by hand at all; just run the MCP server. The manual Flipper steps below +> are for interactive, human-driven debugging in the Flipper desktop UI. + +### Flipper desktop UI + +1. Install **Flipper**: + [fbflipper.com/docs/getting-started](https://fbflipper.com/docs/getting-started/#installation) +2. Install the `flipper-plugin-player-ui-devtools` plugin (see [Building & + installing the plugin](#building--installing-the-plugin) to build it from this + repo). +3. Enable the plugin. +4. Connect to your app, open the plugin, and start debugging. + +### Building & installing the plugin + +Flipper loads desktop plugins from `~/.flipper/installed-plugins`. This repo +provides a recipe that builds the plugin with Bazel and syncs it into that +directory: + +```bash +just install-flipper-client +``` + +Under the hood that recipe: + +1. bazel-builds `//devtools/flipper-plugin:flipper-plugin-player-ui-devtools` + (stamped, so the version is real), then +2. `rsync`s `bazel-bin/devtools/flipper-plugin/flipper-plugin-player-ui-devtools/` + into `~/.flipper/installed-plugins/flipper-plugin-player-ui-devtools/`. + +Restart Flipper, run a mobile app that has a Player UI devtools plugin installed, +and the **Player UI Devtools** plugin appears for that app's connection. + +> **NOTE** +> Re-run `just install-flipper-client` after changing this package (or the +> client) so Flipper picks up the rebuilt bundle. + +### Troubleshooting + +If the plugin doesn't appear, it's usually one of: + +- `~/.flipper/installed-plugins` doesn't contain the plugin — re-run + `just install-flipper-client`. +- Flipper isn't loading installed plugins — when running Flipper **from source**, + start it with `--plugin-marketplace`; as an installed app this is on by default + (verify in settings). +- When running Flipper from source, the plugin isn't listed in the + `FLIPPER_ENABLED_PLUGINS` `.env` var. + +When loaded correctly the plugin shows as **Unavailable** until an app requiring +it connects, then **Disabled** / **Enabled**. + +## Public surface + +`src/index.tsx` exports two things, which is the standard Flipper desktop-plugin +shape: + +- **`plugin(client)`** — the plugin factory. Flipper calls it once per + connection with a typed `PluginClient`, and it returns a + **`CommunicationLayerMethods`** object (`sendMessage` / `addListener` / + `removeListener`). That return value is what the rendered `Panel` consumes via + `usePlugin`. +- **`Component`** — the React UI. It calls `usePlugin(plugin)` to get the + communication layer and renders the client's `Panel` inside a Flipper + `Layout.Container` (with a small scoped style fix). + +### Message flow + +The transport bridges Flipper's typed messaging to the devtools event stream +(both directions carry `MessengerEvent` from +[`@player-devtools/types`](../types)): + +- **Inbound** — on connect, `plugin()` subscribes to Flipper's + **`"message::plugin"`** event and fans each message out to the listeners the + `Panel` registered through `addListener`. +- **Outbound** — `sendMessage` forwards to Flipper via the + **`"message::flipper"`** method, which delivers it down to the connected + mobile app's messenger. + +## Related + +- [`../client`](../client) — the `Panel` and devtools UI this plugin hosts; the + same component the browser extension renders. +- [`../mcp`](../mcp) — the agent-facing devtools surface; an alternative consumer + of the same Player devtools instrumentation. +- [`../README.md`](../../README.md) — the overall Player UI Devtools architecture. diff --git a/devtools/mcp/README.md b/devtools/mcp/README.md new file mode 100644 index 0000000..1d01287 --- /dev/null +++ b/devtools/mcp/README.md @@ -0,0 +1,138 @@ +# @player-devtools/mcp + +An [MCP](https://modelcontextprotocol.io) server that exposes the Player UI +Devtools to AI agents. It lets a model — e.g. Claude — inspect and drive a live +Player instance: list running Players, read their flow / data / logs / config, +inspect plugin state, select a Player, and invoke plugin actions. + +It is the agent-facing sibling of the [browser extension] and the +[Flipper plugin](../flipper-plugin): all three are Devtools *clients* that speak +to the same Player Devtools plugins over the [messenger](../messenger) protocol. +The MCP server connects to those plugins through a running +[`flipper-server`](https://github.com/facebook/flipper) and re-exposes them as +MCP tools over stdio. + +``` +agent (Claude) ──stdio/MCP──▶ MCPServer ──▶ ExtensionClient ──▶ FlipperServerTransport + │ + flipper-server (localhost:52342) + │ + Player Devtools plugins +``` + +## Installation + +The package ships a CLI, `player-devtools-mcp`, which is what an MCP client runs. + +### Register with Claude Code + +Add it as an MCP server with `claude mcp add` — no env vars or tokens are +required: + +```bash +claude mcp add player-devtools -- npx -y @player-devtools/mcp@latest +``` + +That runs the server over stdio via `npx` (no global install needed). To pin a +version, replace `@latest`. To point at a non-default Flipper server, pass the +host/port through the CLI after `--` (see [Transport](#transport)). + +> **NOTE** +> You do **not** need to install or start Flipper first — the server starts and +> manages a shared `flipper-server` for you (see +> [Shared `flipper-server` daemon](#shared-flipper-server-daemon)). + +### Other MCP clients + +Any MCP client that launches a stdio command works — point it at +`npx -y @player-devtools/mcp@latest` (or the `player-devtools-mcp` bin if you've +installed the package). To install the package directly: + +```bash +npm install @player-devtools/mcp +``` + +See [Running](#running) for the bin and the in-repo `just` recipes. + +## Tools + +The server registers the following tools (defined as `ToolDef`s in +[`src/tools`](./src/tools) and registered in one loop in `MCPServer`). Every +player-scoped tool takes an optional `playerId` and falls back to the currently +selected Player when it's omitted. + +| Tool | Args | Returns | +| --- | --- | --- | +| `list_players` | — | All known Player instances and which one is selected. | +| `get_player_status` | `playerId?` | Active status + registered plugin IDs. | +| `get_flow` | `playerId?` | The current flow (from the basic plugin). | +| `get_data` | `playerId?` | The current flow data model. | +| `get_logs` | `playerId?` | Accumulated runtime logs. | +| `get_plugin_data` | `playerId?`, `pluginId`, `dataKey` | A specific data key from any plugin. | +| `describe_plugin` | `playerId?`, `pluginId` | The plugin's capability descriptor — the data keys and actions it exposes. **Call this first** to discover what a plugin supports. | +| `select_player` | `playerId` | Selects a Player as the default target for later calls. | +| `invoke_action` | `playerId?`, `pluginId`, `action`, `payload?` | Invokes a named action (validated against the plugin's declared capabilities). | + +A typical agent flow: `list_players` → `select_player` → `describe_plugin` → +`get_*` / `invoke_action`. + +> **NOTE** +> Tool handlers return *soft errors* (e.g. `{ "error": "player not found" }`) as +> normal results rather than throwing, so a failed call still returns a +> structured payload the agent can read. + +## Transport + +The server is decoupled from how it reaches the Player via the `Transport` +interface. The shipped implementation is `FlipperServerTransport`. + +```ts +import { MCPServer, FlipperServerTransport } from "@player-devtools/mcp"; + +const server = new MCPServer(new FlipperServerTransport({ host, port })); +await server.start(); +``` + +`FlipperServerTransport` options: + +| Option | Default | Description | +| --- | --- | --- | +| `host` | `"localhost"` | Flipper server host. | +| `port` | `52342` | Flipper server WebSocket port. | + +### Shared `flipper-server` daemon + +Multiple MCP processes (one per editor/agent registration) attach to a **single** +`flipper-server` on the fixed port. Because no in-process state can coordinate +separate processes, the transport tracks the daemon with a cross-process +**refcount** — a small file under the OS temp dir guarded by an atomic lock +directory: + +- The first process to attach starts the daemon (detached + `unref`'d so it + outlives that process) and records its PID with `refs: 1`. +- Each subsequent attach increments `refs`. +- Each `close()` decrements `refs`; the **last** one out shuts the daemon down. + +This means you can run several agents against the same devices without each +spawning (or prematurely killing) its own Flipper server. + +## Running + +Via the CLI (wraps `FlipperServerTransport` + `MCPServer` and wires `SIGINT` / +`SIGTERM` to a graceful shutdown): + +```bash +player-devtools-mcp +``` + +From the repo, two `just` recipes are provided: + +```bash +just mcp # bazel run //devtools/mcp:mcp_server +just mcp-inspect # open the MCP inspector against the server +``` + +Register it with an MCP client (e.g. Claude) by pointing the client at the +`player-devtools-mcp` command over stdio. + +[browser extension]: https://github.com/player-ui/browser-devtools diff --git a/devtools/messenger/README.md b/devtools/messenger/README.md index c1cd025..71828f3 100644 --- a/devtools/messenger/README.md +++ b/devtools/messenger/README.md @@ -1,76 +1,217 @@ -# @player-devtools/messenger +# Devtools Messenger + +The **Devtools Messenger** is the transport that connects a Player runtime to a +Devtools client. It is a communication-layer-agnostic, self-sufficient, and +lossless messenger: you give it primitives for sending and receiving on whatever +channel you have (`window.postMessage`, `browser.runtime`, a JS bridge), and it +handles peer discovery, ordering, and recovery on top of that channel. + +It is "self-sufficient" because there is **no central bookkeeper** — every +instance announces itself, tracks its own peers, and recovers its own lost +messages. This is what lets the same protocol work unchanged across a browser +extension, a Flipper desktop connection, and a native JS bridge. + +This directory contains **three** packages — one per platform Player runs on. +The TypeScript `core` is the real implementation; the native packages each +load that compiled bundle into their platform's JS runtime and expose a native +API over it. + +| Package | Name / Target | Role | +| --- | --- | --- | +| [`core`](#core--player-devtoolsmessenger) | `@player-devtools/messenger` | The full implementation — the `Messenger` class, beacon handshake, sequencing, and recovery. The native packages load its compiled bundle. | +| [`jvm`](#native-packages) | `messenger` (`kt_jvm`) | Kotlin wrapper — loads the core bundle via the Player JS runtime and exposes a `Messenger` over serializable `Event`s. | +| [`ios`](#native-packages) | `Messenger` (`ios_library`) | Swift wrapper — constructs the JS `Messenger` in a shared `JSContext` and exposes an `async`/`await` API. | + +The event type shapes and the `MessengerOptions` contract live in +[`@player-devtools/types`](../types). The Devtools plugins that use this +transport are in [`../plugin`](../plugin); the agent-facing server that consumes +its events is [`../mcp`](../mcp). For the overall Devtools architecture, see the +[root README](../../README.md). -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) +## Installation + +Install the package for the platform you are integrating. `` is a +placeholder — published versions are stamped at release time (sources here read +`0.0.0-PLACEHOLDER`); pin the current release when you add the dependency. -Devtools Messenger is a communication layer agnostic, self-sufficient, and lossless messenger designed for seamless communication between instances. It operates independently without a bookkeeper, ensuring no data loss during the communication process. +**Web / TypeScript** (npm): -## Features +```bash +npm install @player-devtools/messenger +``` -- **Self-sufficient**: Operates independently without the need for a bookkeeper. -- **Lossless**: Ensures no data loss during the communication process. -- **Communication Layer Agnostic**: Can be used with any communication layer. +**Android / JVM** (Maven — group `com.intuit.playerui.devtools`, artifact +`messenger`): -## How it Works +```kotlin +implementation("com.intuit.playerui.devtools:messenger:") +``` -The Messenger class sends beacon messages to announce its presence. When it receives a beacon from another instance, it establishes a connection, sending all the events so far. It also keeps track of the messages it has received and checks for lost messages whenever it receives a new message (messages have a sequential id). +**iOS** (Swift Package Manager — a product of the `PlayerUIDevtools` package at +https://github.com/player-ui/player): -## Installation +```swift +.product(name: "PlayerUIDevtoolsMessenger", package: "PlayerUIDevtools") +``` -To install Devtools Messenger, run the following command, using your preferred package manager: +## How it works -```sh -npm @player-devtools/messenger +Two `Messenger` instances find and synchronize with each other without any +shared coordinator. Each instance sends a periodic **beacon** to announce its +presence. When an instance hears a beacon from a new peer, it records the +connection and immediately replays every event it has buffered so far — so a +peer that connects late still receives the full history. + +``` + Player runtime Devtools client + ┌───────────────┐ ┌───────────────┐ + │ Messenger │ ──── MESSENGER_BEACON ───────▶ │ Messenger │ + │ context: │ ◀──── MESSENGER_BEACON ─────── │ context: │ + │ "player" │ │ "devtools" │ + │ │ ──── MESSENGER_EVENT_BATCH ───▶ │ │ (replay history + │ │ │ │ on new connection) + │ │ ──── sequenced events ───────▶ │ │ + │ │ ◀── MESSENGER_REQUEST_LOST ─── │ │ (gap detected) + │ │ ──── MESSENGER_EVENT_BATCH ───▶ │ │ (missing events) + └───────────────┘ └───────────────┘ ``` -## Usage +The two instances must be in **different contexts** (`"player"` vs. +`"devtools"`); a messenger ignores traffic from its own context and from +itself, so a player never talks to another player over the same channel. + +### Ordering and recovery + +Each targeted (non-internal) message carries a **sequential transaction id** per +connection. The receiver uses those ids to stay lossless: + +- **Duplicate detection** — a message whose id has already been seen is dropped. +- **Lost-event recovery** — if an arriving id skips ahead of what the receiver + expects, it sends a `MESSENGER_REQUEST_LOST_EVENTS` (carrying its + `messagesReceived` count) and waits; the sender replies with a batch of the + missing events. A `desync` flag prevents requesting the same gap twice. +- **Broadcasts** — beacons and other untargeted internal events are stamped with + id `-1`. They are delivered to everyone but are not sequenced, so they do not + advance the receiver's `messagesReceived` counter — otherwise the next + targeted message would look like a duplicate and be dropped. + +On teardown, `destroy()` sends a `MESSENGER_DISCONNECT` to each known peer so +they can drop the connection promptly rather than waiting for a timeout. -Here's a basic example of how to use Devtools Messenger: +> **NOTE** +> Connection bookkeeping (events and connections) is **static**, shared across +> all `Messenger` instances in the same JS context. `Messenger.reset()` clears +> it for every instance — useful in tests, but never call it while a live +> connection is in flight. + +## `core` — `@player-devtools/messenger` + +The TypeScript implementation. `Messenger` is generic over your event union +`T` (a union of `BaseEvent` shapes), so the events you send and +receive stay fully typed. ```ts -import { Messenger } from "devtools-messenger"; +import { Messenger } from "@player-devtools/messenger"; const messenger = new Messenger({ context: "devtools", - target: "player", messageCallback: (message) => dispatch(message), sendMessage: (message) => browser.runtime.sendMessage(message), - addListener: (callback) => { - browser.runtime.onMessage.addListener(callback); - }, - removeListener: (callback) => { - browser.runtime.onMessage.removeListener(callback); - }, + addListener: (callback) => browser.runtime.onMessage.addListener(callback), + removeListener: (callback) => + browser.runtime.onMessage.removeListener(callback), + logger: console, }); ``` -## API Reference - -### Messenger Class - -The main class in Devtools Messenger is the `Messenger`. It is responsible for sending and receiving messages. - -#### Constructor - -The `Messenger` class constructor takes an options object with the following properties: - -- `context`: The context to use for this instance. It can be either "player" or "devtools". -- `target`: The target for the messages. -- `messageCallback`: A callback function to handle messages. -- `sendMessage`: A function to send messages. -- `addListener`: A function to add a listener. -- `removeListener`: A function to remove a listener. +### Constructor options + +`new Messenger(options: MessengerOptions)`. The full +[`MessengerOptions`](../types) contract: + +| Option | Type | Required | Description | +| --- | --- | --- | --- | +| `context` | `"player" \| "devtools"` | yes | Which side this instance is on. It ignores traffic from its own context. | +| `sendMessage` | `(message) => Promise` | yes | Sends a message over your channel (e.g. `window.postMessage`, `browser.runtime.sendMessage`). | +| `addListener` | `(callback) => void` | yes | Subscribes the messenger's handler to incoming messages on your channel. | +| `removeListener` | `(callback) => void` | yes | Unsubscribes that handler; used by `destroy()`. | +| `messageCallback` | `(message) => void` | yes | Invoked with each delivered (deduped, in-order) message. | +| `logger` | `{ log: (...args) => void }` | yes | Sink for debug logging. Only used when `debug` is on. | +| `id` | `string` | no | This instance's unique id. Generated with `tiny-uid` if omitted. | +| `beaconIntervalMS` | `number` | no | Milliseconds between beacons. Defaults to `1000`. | +| `debug` | `boolean` | no | When `true`, emits debug messages through `logger`. Defaults to `false`. | +| `handleFailedMessage` | `(message) => void` | no | Called with the full transaction when `sendMessage` rejects. | + +> **NOTE** +> There is **no `target` constructor option**. Targeting is per-message: set +> `target` on an individual event to address a specific peer; omit it to +> broadcast. ### Methods -The Messenger class has the following methods: - -- `sendMessage(message: T | string)`: Sends a message. The message can be a string or an object of type T, where T is the type of your events. -- `destroy()`: Destroys the messenger instance. It removes all listeners and sends a disconnect message to all connections. - -### Error Handling +| Method | Description | +| --- | --- | +| `sendMessage(message: T \| string): Promise` | Buffers the event (so late peers can be replayed), stamps it with transaction metadata, and sends it. A JSON string is parsed first; a parse failure rejects. A send failure routes the transaction to `handleFailedMessage`. | +| `destroy(): void` | Stops the beacon interval, removes the listener, sends a `MESSENGER_DISCONNECT` to each known peer, and resets the shared static state. | +| `static reset(): void` | Clears the shared static events/connections record for all instances in the context. | + +## Native packages + +The `jvm` and `ios` packages do **not** reimplement the protocol. Each loads the +compiled core bundle (`Messenger.native.js`) into the platform's Player JS +runtime and exposes a native API that forwards to the JS instance — so ordering, +recovery, and the handshake behave identically to `core`. They sit at coverage +parity with `core` but intentionally thinner. + +### JVM — `com.intuit.playerui.devtools.Messenger` + +A `JSScriptPluginWrapper` (Kotlin) that loads the bundled core module into a +`Runtime`, installs `SetTimeoutPlugin` (and polyfills `setInterval`) so the +beacon timer works, then constructs the JS `Messenger` with a serialized +`Options`. Messages are modeled as serializable `Event` subclasses (see +`Event.kt`). + +```kotlin +val messenger = Messenger( + Messenger.Options( + context = TransactionMetaData.Context.DEVTOOLS, + sendMessage = { event -> /* send over your channel */ }, + addListener = { callback -> /* subscribe */ }, + removeListener = { callback -> /* unsubscribe */ }, + messageCallback = { event -> /* handle */ }, + logger = Messenger.Logger { args -> println(args.joinToString(" ")) }, + ), +) +``` -If a message fails to be sent, the `handleFailedMessage` function will be called with the failed message as an argument. This function can be provided in the options object when creating a Messenger instance. +- `Options` mirrors the TS contract: `context`, `sendMessage`, `addListener`, + `removeListener`, `messageCallback`, optional `handleFailedMessage`, `id`, + `beaconIntervalMS`, `debug`, and a `logger` (`Logger` functional interface). +- The wrapper hijacks `addListener`/`removeListener` so it can register and + remove handlers with stable JVM references (JS cannot compare function + identity across the bridge). +- API: `sendMessage(Event)`, `sendMessage(JsonElement)`, `destroy()`, `reset()`. + +### iOS — `Messenger` (Swift) + +A Swift class that constructs the JS `Messenger` in a shared `JSContext` (pulled +from its [`MessengerOptions`](../types), so options and messenger share a +context) and exposes an `async`/`await` API by bridging the JS `Promise` +returned from `sendMessage`. + +```swift +let messenger = try Messenger(options: messengerOptions) +try await messenger.sendMessage(message) // Message or JSON String +messenger.destroy() +``` -## Contributing +- `MessengerOptions` (in [`../types`](../types)) requires `id`, `jsContext`, + `context` (`.player` / `.devtools`), `logger` (a `MessengerLogger`), + `sendMessage`, `addListener`, `removeListener`, and `messageCallback`, with + optional `beaconIntervalMS`, `isDebug`, and `handleFailedMessage`. +- `SharedMessengerLayer.reset(context:logger:)` bridges to the static JS + `Messenger.reset()`, since generic Swift types cannot hold static functions. -We welcome contributions! Please see our contributing guide for more details. +> **NOTE** +> All Swift `MessengerOptions` share the same `JSContext`, mirroring the static, +> per-context bookkeeping in `core`. diff --git a/devtools/plugin/README.md b/devtools/plugin/README.md index 9594cb8..0eea083 100644 --- a/devtools/plugin/README.md +++ b/devtools/plugin/README.md @@ -1,36 +1,356 @@ -# Devtools Plugins +# Devtools Plugin Foundations -As a plugin-driven system, Player debugging through Devtools may require capabilities specific to your integration. These packages contain the foundations for custom Player Devtools plugins, but are not complete Devtools plugins themselves. For debugging core Player functionality, [BasicDevtoolsPlugin](../plugins/basic) is provided to expose such state through the Devtools clients. +The **`plugin/` family** is the platform-agnostic foundation every Player +Devtools plugin is built on. These packages are **not** a complete Devtools +plugin themselves — they provide the base classes and platform wrappers that a +concrete plugin (like the [Basic Devtools Plugin](../plugins/basic)) extends to +instrument a running Player and talk to the Devtools clients. -There is a browser extension Devtools client for web Player use cases, and a [Flipper](https://github.com/facebook/flipper) plugin for mobile Player use cases. The base plugin implementations for each platform are configured to communicate with these respective clients. +A Devtools plugin works by running a **Player instance for the debugging +experience itself**: the plugin publishes the instrumented Player's content and +data updates to the clients, which render them using the +[devtools-assets](https://github.com/player-ui/devtools-assets). The clients are +the browser extension (web), the [Flipper](https://github.com/facebook/flipper) +plugin (mobile), and the [MCP server](../mcp). The base plugins here are +pre-wired to communicate with those clients through the +[`messenger`](../messenger). + +If you're debugging core Player functionality, use the +[`BasicDevtoolsPlugin`](../plugins/basic) directly — it's also the best +**reference implementation** to read alongside this guide. If you need +capabilities specific to your integration, extend the base classes documented +here. + +This directory contains **six** packages: the TypeScript `core` holds the +shared state-management and instrumentation contract, `react` wraps it for web, +and the `jvm`, `android`, `ios`, and `swiftui` packages adapt the same `core` +bundle to each native runtime. + +**TypeScript (shared + web):** + +| Package | Name | Role | +| --- | --- | --- | +| [`core`](#core--player-devtoolsplugin) | `@player-devtools/plugin` | Platform-agnostic base — `DevtoolsPlugin` class, the store/reducer, the interactions cursor, and the `DevtoolsHandler` contract. Compiles to a `DevtoolsPlugin.native.js` bundle the native wrappers load. | +| [`react`](#react--player-devtoolsplugin-react) | `@player-devtools/plugin-react` | Web wrapper — abstract `ReactDevtoolsPlugin` base that binds a core plugin to `ReactPlayer` and connects the messenger via both Flipper and `window.postMessage`. | + +**Native platform wrappers** (each loads `core`'s `DevtoolsPlugin.native.js` +into the platform's Player JS runtime and adapts it to the platform's plugin +protocol): + +| Package | Target | Role | +| --- | --- | --- | +| [`jvm`](#jvm--devtoolsplugin-kotlin) | `kt_jvm` (`plugin`) | `DevtoolsPlugin` (Kotlin) — `NodeWrapper` over the core JS plugin, loaded through `ModuleLoader`. | +| [`android`](#android--androiddevtoolsplugin) | `kt_android` (`plugin-android`) | abstract `AndroidDevtoolsPlugin` — Android wrapper that owns the Flipper connection and messenger lifecycle. | +| [`ios`](#ios--basedevtoolsplugin-swift) | `swiftui_plugin` (`PlayerUIDevtoolsPlugin`) | `BaseDevtoolsPlugin` protocol — `JSBasePlugin` bridge into the core JS plugin. | +| [`swiftui`](#swiftui--devtoolsplugin-swift) | `swiftui_plugin` (`PlayerUIDevtoolsSwiftUIPlugin`) | `DevtoolsPlugin` protocol — `NativePlugin` that owns the Flipper connection + messenger lifecycle. | + +## Installation + +You don't usually install these directly — you install a concrete plugin (e.g. +[`@player-devtools/basic-plugin-react`](../plugins/basic)) that depends on them. +Install a `plugin/` package only when you're building your own Devtools plugin +and wrapping `core` yourself. + +**Web / TypeScript** (npm): + +```bash +npm install @player-devtools/plugin # core base class +npm install @player-devtools/plugin-react # React/web wrapper +``` + +**Android / JVM** (Maven — group `com.intuit.playerui.devtools`): + +```kotlin +// JVM +implementation("com.intuit.playerui.devtools:plugin:") +// Android +implementation("com.intuit.playerui.devtools:plugin-android:") +``` + +**iOS / SwiftUI** (Swift Package Manager — products of the `PlayerUIDevtools` +package at https://github.com/player-ui/player): + +```swift +.product(name: "PlayerUIDevtoolsPlugin", package: "PlayerUIDevtools") // iOS base (plugin/ios) +.product(name: "PlayerUIDevtoolsSwiftUIPlugin", package: "PlayerUIDevtools") // SwiftUI (plugin/swiftui) +``` + +> NOTE +> Published versions are stamped at release time (sources here read +> `0.0.0-PLACEHOLDER`); pin the current release version when you add the +> dependency. + +## Architecture + +The family follows the layered Devtools pattern: a platform-agnostic **core** +plugin owns all state and behavior, and a thin **platform** wrapper configures +the [messenger](../messenger) and injects the core plugin into the host Player. +Native wrappers don't reimplement instrumentation — they load the compiled +`core` bundle and bridge it. + +``` +host Player (web / Android / JVM / iOS) + │ platform wrapper injects core plugin + wires the messenger + ▼ +DevtoolsPlugin (core) ──messages──▶ Messenger ──▶ client (extension / Flipper / MCP) + ▲ store + reducer │ + └──────── processInteraction ◀──── interactions ◀──────┘ +``` + +State flows **out** as messages the store emits and the messenger forwards to +the client. Interactions flow **in** as `PLAYER_DEVTOOLS_PLUGIN_INTERACTION` +transactions that the store appends and `processInteraction` dispatches on. ## Building your own plugin -The Player Devtools clients were built to integrate with any arbitrary Player Devtools plugin. This actually works by using a Player instance for the debugging experience itself, publishing the Player content and data updates from the plugin to the clients. These Players are configured to use the [devtools-assets](https://github.com/player-ui/devtools-assets) for rendering, as such devtools plugin content is required to conform to those asset APIs. +Implementing a core plugin means **extending `DevtoolsPlugin`** and supplying +`pluginData`, `apply`, and `processInteraction`. `pluginData` is the client +Player content that defines the actual debugging experience for your plugin. + +- `apply(player)` is where you tap platform-agnostic Player / plugin APIs to + gather state for the clients. +- `processInteraction(interaction)` is where you handle interactions coming back + from the clients. + +> NOTE +> Calling `super.apply(player)` is important — it emits the init events the +> clients need. But gate the expensive work behind `checkIfDevtoolsIsActive()` +> first to avoid doing work when Devtools is inactive. The same applies to any +> platform-specific `apply` override. + +Even with no platform-specific capabilities to debug, you still need a +**platform-specific plugin** to configure the messenger. These extend +`{Platform}DevtoolsPlugin` (e.g. `ReactDevtoolsPlugin`, `AndroidDevtoolsPlugin`) +and provide the core plugin to use — either your own `DevtoolsPlugin` subclass +or the base `DevtoolsPlugin` configured with `pluginData`. + +--- + +## `core` — `@player-devtools/plugin` + +The platform-agnostic base. Exports the `DevtoolsPlugin` class plus the +`DevtoolsHandler` and `DevtoolsPluginOptions` types. It compiles to a +`DevtoolsPlugin.native.js` bundle (via `js_pipeline`'s `native_bundle`) that the +native wrappers load into their Player JS runtime. + +```ts +import { DevtoolsPlugin, type DevtoolsHandler } from "@player-devtools/plugin"; +``` + +### `DevtoolsPlugin` + +`DevtoolsPlugin implements PlayerPlugin, DevtoolsHandler`. It is constructed with +`DevtoolsPluginOptions`: + +```ts +type DevtoolsPluginOptions = { + playerID: string; + pluginData: PluginData; // the client Player content + capability descriptor + handler: DevtoolsHandler; +}; +``` + +`pluginID` and `playerID` are exposed as getters (`pluginID` reads +`pluginData.id`). + +| Member | Responsibility | +| --- | --- | +| `apply(player)` | Bails when Devtools is inactive, otherwise calls `dispatchPlayerInit()` to publish the plugin's content. Subclasses override to add taps. | +| `processInteraction(interaction)` | Forwards the interaction to the `handler`, then dispatches a `SELECTED_PLAYER_CHANGE` when the interaction is `player-selected`. Subclasses override to handle their own interaction types. | +| `registerMessenger(messenger)` | Subscribes to the store and forwards each newly-added message to the messenger; returns an `Unsubscribe`. | +| `checkIfDevtoolsIsActive()` | Delegates to the handler; logs the inactive warning once if Devtools is off. | +| `store` | The reducer-backed store (`useStateReducer(reducer, INITIAL_STATE)`) holding `messages`, `plugins`, `interactions`, and `currentPlayer`. | + +### The store, reducer, and interactions cursor + +The store is a reducer (`reducer.ts`) over `Transaction` +covering `PLAYER_INIT`, `PLUGIN_DATA_CHANGE`, `PLUGIN_INTERACTION`, and +`SELECTED_PLAYER_CHANGE`. Updates are applied immutably with +[`immer`](https://immerjs.github.io) and `dsetAssign` from +[`@player-devtools/utils`](../utils/core), so untouched branches keep reference +identity. + +The constructor subscribes to the store and watches `interactions`. It tracks a +`lastProcessedInteraction` **cursor** and only processes interactions appended +since the last pass. > NOTE -> [BasicDevtoolsPlugins] is a great plugin to include in your Player configuration, but will also serve as a great reference for implementing your own plugin. +> The cursor is advanced **before** the new interactions are processed. +> `processInteraction` can dispatch synchronously (e.g. `SELECTED_PLAYER_CHANGE`), +> which re-enters the same subscriber — advancing first makes that re-entrant +> pass a no-op instead of reprocessing the same interactions. + +State publishes outward via `dispatchDataUpdate(data?)`, which deep-merges into +`plugins[pluginID].flow.data` and dispatches a data-change transaction only when +the data actually changed (guarded by `dequal`). + +### `DevtoolsHandler` + +The contract the platform layer supplies so `core` can defer +platform-specific concerns: + +```ts +type DevtoolsHandler = { + processInteraction(interaction: DevtoolsPluginInteractionEvent): void; + checkIfDevtoolsIsActive(): boolean; + log?(message: string): void; +}; +``` + +--- + +## `react` — `@player-devtools/plugin-react` -### Core plugin implementation +The web wrapper. Exports the abstract `ReactDevtoolsPlugin` base class (plus +re-exports `genDataChangeTransaction` and `DevtoolsPluginOptions` from `core`). -Interaction with platform-agnostic Player APIs should be captured with platform-agnostic Devtools plugins and wrapped for use on multiple platforms. The platform implementations would have the opportunity to expand on the core plugin, but don't necessarily need to. +```ts +import { ReactDevtoolsPlugin } from "@player-devtools/plugin-react"; +``` -Implementing the core plugin is done by extending the `DevtoolsPlugin` and providing `pluginData`, `apply` and `processInteraction` implementations. `pluginData` contains the client Player content, defining the actual client experience for this plugin. +### `ReactDevtoolsPlugin` -`apply` is where you'll tap into platform-agnostic Player or plugin APIs to gather info for informing the Devtools clients. `processInteraction` is where you'll handle interactions from the Devtools clients. +`ReactDevtoolsPlugin implements ReactPlayerPlugin, DevtoolsHandler`. It's +**abstract**: a concrete subclass supplies the `corePlugin` (a `DevtoolsPlugin`), +and the base derives `playerID` and `store` from it. + +- `checkIfDevtoolsIsActive()` reads `localStorage.getItem("player-ui-devtools-active") === "true"`, set by the browser extension popup. +- `applyReact(reactPlayer)` bails when Devtools is inactive, then taps + `reactPlayer.hooks.webComponent` to wrap the rendered component. The wrapper + builds a [`Messenger`](../messenger) (context `"player"`, dispatching incoming + messages into the core store) and calls `corePlugin.registerMessenger`, + destroying the messenger on unmount. +- `processInteraction` is a no-op at this layer by default — the core plugin + owns interaction handling. + +### `useCommunicationLayer` + +The hook that supplies the messenger's transport. It connects over **both** +channels at once, fanning each `sendMessage` / `addListener` / `removeListener` +out to every registered callback: + +| Channel | When | How | +| --- | --- | --- | +| **Flipper** | `localStorage` `player-ui-devtools-flipper-active === "true"` | Starts a `js-flipper` client under id `player-ui-devtools`, sends on `message::plugin`, receives on `message::flipper`. | +| **`window.postMessage`** | always | Posts messages to `window` and listens for `message` events — this is how the browser extension content script communicates. | > NOTE -> Calling `super.apply(player)` is important to ensure init events are sent to the clients, but it is best practice to `checkIfDevtoolsIsActive` before calling `super.apply` to avoid unnecessary work. This also applies to the platform-specific implementations, if they override `apply` as well. +> Flipper is opt-in via the extension popup; if disabled it logs a warning and +> only the `window.postMessage` channel is active. Web devtools work without +> Flipper. + +--- + +## `jvm` — `DevtoolsPlugin` (Kotlin) + +`com.intuit.playerui.devtools.DevtoolsPlugin` is a `NodeWrapper` over the core +JS plugin — it does **not** reimplement the logic, it bridges to it. It +implements `DevtoolsHandler` and `PlayerPlugin`, exposing `pluginID`, `playerID`, +`store`, `checkIfDevtoolsIsActive()`, `processInteraction(...)`, +`registerMessenger(...)`, and `apply(player)` as invokables on the underlying JS +`Node`. + +Construction goes through a `Runtime` extension that loads the bundled core +module via `ModuleLoader` (from `devtools/plugin/core/dist/DevtoolsPlugin.native.js`) +and instantiates the JS plugin with `Options(playerID, pluginData, handler)`: -### Platform-specific plugin implementation +```kotlin +val plugin = runtime.DevtoolsPlugin( + DevtoolsPlugin.Options(playerID = "my-player", pluginData = data, handler = myHandler), +) +``` -Even if you don't have a core plugin for your use case, the platform-specific Devtools plugins still rely on the base core plugin for state management. And even if you don't have platform-specific capabilities to debug, you'll still need to implement a platform-specific plugin to configure the messenger. +The package also exports the `DevtoolsHandler` interface (with a `KSerializer` +that bridges the handler callbacks across the JS boundary), the `PluginStore` +wrapper, and `ModuleLoader`. The JVM messenger comes from +[`messenger/jvm`](../messenger). -Similarly to the core plugin, platform-specific plugins will extend `{Platform}DevtoolsPlugin` (i.e. `ReactDevtoolsPlugin` & `AndroidDevtoolsPlugin`) and must provide the core devtools plugin to use, which will either be your platform-agnostic `DevtoolsPlugin` implementation or the base `DevtoolsPlugin` configured with `pluginData` (since that isn't coming from a core implementation). +--- -`apply` and `processInteraction` can also be overridden to provide platform-specific functionality. +## `android` — `AndroidDevtoolsPlugin` +`com.intuit.playerui.devtools.AndroidDevtoolsPlugin` is the +abstract Android wrapper. It implements `DevtoolsHandler`, `AndroidPlayerPlugin`, +and `RuntimePlugin`. A concrete subclass overrides +`Runtime<*>.buildCorePlugin(): T` to construct its core plugin (typically via the +`jvm` package's `Runtime.DevtoolsPlugin(...)`). + +- `checkIfDevtoolsIsActive()` returns true only when a `PlayerDevtoolsFlipperPlugin` + is registered on the active `AndroidFlipperClient`. +- `apply(androidPlayer)` bails when inactive, builds a [`Messenger`](../messenger) + bound to the Flipper plugin's `sendMessage` / `addListener` / `removeListener`, + registers it on the core plugin, then applies the core plugin to the Player. + It taps `androidPlayer.hooks.state` to deregister the listener when the Player + reaches `ReleasedState`. + +`PlayerDevtoolsFlipperPlugin` is the `FlipperPlugin` (id `player-ui-devtools`) +that owns the `FlipperConnection`, fans `message::flipper` messages out to +listeners, and sends on `message::plugin` — mirroring the web Flipper layer. + +```kotlin +// concrete subclasses (e.g. BasicAndroidDevtoolsPlugin) plug into AndroidPlayer +AndroidPlayer(MyAndroidDevtoolsPlugin(id = "my-player"), /* ... */) +``` + +--- + +## `ios` — `BaseDevtoolsPlugin` (Swift) + +`BaseDevtoolsPlugin` is a protocol refining `JSBasePlugin` — the bridge into the +core JS plugin. A default `extension` implements `pluginID`, `playerID`, `store`, +`isActive`, and `registerMessenger(messenger:)` by reading properties and +invoking methods on the underlying `pluginRef` JS value (e.g. +`checkIfDevtoolsIsActive`, `registerMessenger`). + +Supporting types: + +- **`DevtoolsPluginOptions`** — packages `playerID`, `handler`, and optional + `pluginData` into the `jsCompatible` dictionary passed into the core plugin. +- **`PluginData`** — the descriptor (`id`, `version`, `name`, `description`, + `flow`) handed to the core JS plugin. +- **`PluginStore`** — wraps the JS store value, exposing `dispatch(event:)`. +- **`DevtoolsHandler`** ([`DevtoolsHandler.swift`](ios/Sources/DevtoolsHandler.swift)) — + the `isActive` / `processInteraction(interaction:)` contract, with a + `jsCompatible(context:)` extension that exports the callbacks into JS. + +This layer points the runtime at the bundled `DevtoolsPlugin.native.js` and +bridges the contract — it does **not** own a Flipper connection or messenger +lifecycle. That's the SwiftUI layer's job. The protocol is built to be adopted +by a concrete SwiftUI plugin. + +--- + +## `swiftui` — `DevtoolsPlugin` (Swift) + +`DevtoolsPlugin` is the protocol you adopt for a **SwiftUI** Player. It refines +`BaseDevtoolsPlugin` and `NativePlugin`, adding the runtime wiring the base +layer can't own: + +- `flipperPlugin: DevtoolsFlipperPlugin` — the Flipper connection + ([`DevtoolsFlipperPlugin.swift`](ios/Sources/DevtoolsFlipperPlugin.swift), id + `player-ui-devtools`), which sends on `message::plugin` and dispatches + `message::flipper` messages to registered listeners (keyed by `UUID`). +- `messenger: Messenger?` — a reference the conformer holds so the messenger + stays alive. +- `listeners: [UUID]` — the listener ids registered with the Flipper plugin. + +A default `extension` implements `apply(player:)`: it resolves the JS context, +builds a [`Messenger`](../messenger) bound to the Flipper plugin's send/listen +callbacks (dispatching incoming messages into the core `store`), and registers +it on the core plugin. + +> NOTE +> The `apply` extension only runs when the conformer is a `NativePlugin`. A +> concrete SwiftUI plugin supplies the stored `flipperPlugin` / `messenger` / +> `listeners` properties the protocol declares. ## Enabling devtools -To enable devtools, simply add the platform-specific devtools plugins to your Player configuration, and activate connection to the devtools clients. For mobile, this is done by configuring the [`FlipperClient`](https://github.com/facebook/flipper/blob/main/docs/getting-started/android-native.mdx) in your app -- for web, activate through the browser extension popup. +Add the platform-specific Devtools plugin (a concrete subclass of the base +classes here, e.g. [`BasicReactDevtoolsPlugin`](../plugins/basic)) to your +Player configuration, then activate a client connection — the browser extension +popup for web, or the `FlipperClient` for mobile. See +[`plugins/basic`](../plugins/basic) for a full reference implementation, the +[`messenger`](../messenger) and [`types`](../types) packages for the transport +and event contracts, and [the devtools root README](../../README.md) for the +overall architecture. diff --git a/devtools/plugins/basic/README.md b/devtools/plugins/basic/README.md new file mode 100644 index 0000000..22f676d --- /dev/null +++ b/devtools/plugins/basic/README.md @@ -0,0 +1,301 @@ +# Basic Devtools Plugin + +The **Basic Devtools Plugin** is the standard, batteries-included Player Devtools +plugin. It instruments a running Player instance to expose its core runtime +state — flow, data model, logs, and configuration — to the Devtools clients +(the [browser extension], the [Flipper plugin](../../flipper-plugin), and the +[MCP server](../../mcp)), and it accepts a small set of interactions back from +those clients (evaluate an expression, override the running flow). + +It is also the **reference implementation** for building your own plugin. If you +need to debug capabilities specific to your integration, this is the best +plugin to read alongside the [plugin authoring guide](../../plugin). + +This directory contains **seven** packages that compose into the full plugin +across every platform Player runs on. The TypeScript `core` holds all the +instrumentation logic; the `content` package defines the Devtools UI and +capability descriptor; and each platform — web, Android, JVM, iOS, SwiftUI — +has a thin wrapper that loads `core` into its runtime and configures the +messenger. + +**TypeScript (shared + web):** + +| Package | Name | Role | +| --- | --- | --- | +| [`core`](#core--player-devtoolsbasic-plugin) | `@player-devtools/basic-plugin` | Platform-agnostic instrumentation — taps Player hooks, handles interactions. The other platforms load this bundle. | +| [`content`](#content--player-devtoolsbasic-plugin-content) | `@player-devtools/basic-plugin-content` | The Devtools UI flow (DSL content) plus the plugin's `id`, capability descriptor, and interaction constants. | +| [`react`](#react--player-devtoolsbasic-plugin-react) | `@player-devtools/basic-plugin-react` | React/web wrapper — binds the core plugin to `ReactPlayer` and renders a per-Player wrapper component. | + +**Native platform wrappers** (each bundles `core`'s `BasicDevtoolsPlugin.native.js` +and wraps it, mirroring how `react` wraps `core`): + +| Package | Target | Role | +| --- | --- | --- | +| [`jvm`](#native-platform-wrappers) | `kt_jvm` | `BasicDevtoolsPlugin` (Kotlin) — loads the core bundle into the Player JS runtime via `ModuleLoader`. | +| [`android`](#native-platform-wrappers) | `kt_android` | `BasicAndroidDevtoolsPlugin` — Android wrapper over `jvm`, adds the debug overlay styling. | +| [`ios`](#native-platform-wrappers) | `ios_library` | `BaseBasicDevtoolsPlugin` (Swift) — `JSBasePlugin` that loads the core bundle. | +| [`swiftui`](#native-platform-wrappers) | `swiftui_plugin` | `BasicDevtoolsPlugin` (SwiftUI) — wraps the iOS base plugin and owns the Flipper connection + messenger lifecycle. | + +## Installation + +Install the package for the platform you're integrating. The web/TS packages +ship to npm; the Kotlin packages to Maven; the Swift packages via SPM as products +of the `PlayerUIDevtools` Swift package. + +**Web / TypeScript** (npm): + +```bash +npm install @player-devtools/basic-plugin-react +# bring in @player-devtools/basic-plugin / -content directly only if you wrap core yourself +``` + +**Android / JVM** (Maven — group `com.intuit.playerui.devtools.plugins`): + +```kotlin +// Android +implementation("com.intuit.playerui.devtools.plugins:basic-android:") +// JVM +implementation("com.intuit.playerui.devtools.plugins:basic:") +``` + +**iOS / SwiftUI** (Swift Package Manager — products of the `PlayerUIDevtools` +package at https://github.com/player-ui/player): + +```swift +.product(name: "PlayerUIDevtoolsBasicPlugin", package: "PlayerUIDevtools") // SwiftUI +.product(name: "PlayerUIDevtoolsBaseBasicDevtoolsPlugin", package: "PlayerUIDevtools") // iOS base +``` + +> Published versions are stamped at release time (sources here read +> `0.0.0-PLACEHOLDER`); pin the current release version when you add the +> dependency. + +## Architecture + +The Basic plugin follows the layered Devtools plugin pattern: a platform-agnostic +**core** plugin owns all state and behavior, the **content** package defines what +the Devtools UI looks like and what the plugin advertises, and a thin +**platform** wrapper (here, `react`) configures the messenger and injects the +core plugin into the host Player. + +``` +Player runtime + │ (hook taps: dataController, logger, onStart, view, expressionEvaluator) + ▼ +BasicDevtoolsPlugin (core) ──dispatchDataUpdate──▶ messenger ──▶ Devtools client / MCP + ▲ │ + └──────────────── processInteraction ◀──── PLUGIN_INTERACTION ◀───────┘ +``` + +State flows **out** as `dispatchDataUpdate` calls keyed by data key (`flow`, +`data`, `logs`, `playerConfig`, `history`). Interactions flow **in** as +`PLAYER_DEVTOOLS_PLUGIN_INTERACTION` events that `processInteraction` dispatches +on by type. + +--- + +## `core` — `@player-devtools/basic-plugin` + +`BasicDevtoolsPlugin` extends the base [`DevtoolsPlugin`](../../plugin) and is +constructed with everything except `pluginData` — it supplies +[`BasicPluginData`](#content--player-devtoolsbasic-plugin-content) for you. + +```ts +import { BasicDevtoolsPlugin } from "@player-devtools/basic-plugin"; + +const plugin = new BasicDevtoolsPlugin({ playerID, handler }); +``` + +### What it taps + +`apply(player)` registers the plugin's logger first (so logs are captured even +before Devtools is confirmed active), bails early if Devtools is inactive, then +taps: + +| Hook | Captured as | Published to data key | +| --- | --- | --- | +| `player.hooks.dataController` → `onUpdate` | `data` | `data` | +| `player.logger.hooks.log` | `logs` | `logs` | +| `player.hooks.onStart` | `flow` | `flow` | +| `player.hooks.view` | `view` | — | +| `player.hooks.expressionEvaluator` | `expressionEvaluator` | — | +| `player.getVersion()` / `getPlugins()` | `playerConfig` | `playerConfig` | + +Data-model updates are applied immutably with [`immer`](https://immerjs.github.io) +and [`dsetAssign`](../../utils/core), which deep-assigns into the draft while +preserving reference identity for untouched branches. + +> **NOTE** +> `apply` calls `super.apply(player)` to emit the init events the clients need. +> When overriding `apply`, gate the expensive work behind `checkIfDevtoolsIsActive()`. + +### Interactions it handles + +`processInteraction` first calls `super.processInteraction` (so the base class +can run platform-specific handling), then dispatches on the interaction type: + +- **`evaluate-expression`** (`INTERACTIONS.EVALUATE_EXPRESSION`) — evaluates the + payload string against the captured expression evaluator and appends an + `Evaluation` (`{ id, expression, result, severity? }`) to the `history` data + key. Evaluator errors are caught and returned as `severity: "error"` results + rather than thrown. +- **`override-flow`** (`INTERACTIONS.OVERRIDE_FLOW`) — parses the payload as flow + JSON and restarts the Player with it via the captured `player.start`. Parse + failures are logged and ignored. + +--- + +## `content` — `@player-devtools/basic-plugin-content` + +Defines the data the plugin advertises and the UI the Devtools clients render. +It has no runtime Player dependency — only `@player-devtools/types`. + +```ts +import { + BasicPluginData, + PLUGIN_ID, + INTERACTIONS, +} from "@player-devtools/basic-plugin-content"; +``` + +- **`BasicPluginData`** — the `PluginData` descriptor: `id`, `name`, `version`, + the compiled Devtools UI `flow`, and a `capabilities` block. The `capabilities` + descriptor is what [`describe_plugin`](../../mcp) returns — it documents each + published `data` key and each accepted `action` so agents can discover the + plugin's surface without reading source. +- **`PLUGIN_ID`** — `"player-ui-basic-devtools-plugin"`. The id every Basic plugin + registers under. +- **`INTERACTIONS`** — the interaction-type string constants + (`EVALUATE_EXPRESSION`, `OVERRIDE_FLOW`) shared between the content descriptor + and the core plugin's `processInteraction`. +- **`VIEWS_IDS`** — view ids used by the Devtools UI flow (`Config`, `Flow`, + `Logs`, `Console`, `Editor`). + +### Versioning + +The `version` field is stamped from the `__VERSION__` global injected at build +time (`dsl_compile`), falling back to `"unstamped"` in unbuilt/dev contexts. The +`flow` itself is generated from the DSL source into `_generated/flow.json`, so it +conforms to the [devtools-assets](https://github.com/player-ui/devtools-assets) +asset APIs the clients render. + +--- + +## `react` — `@player-devtools/basic-plugin-react` + +`BasicReactDevtoolsPlugin` is the plugin you add to a **web** Player. It extends +[`ReactDevtoolsPlugin`](../../plugin/react), constructs a `BasicDevtoolsPlugin` +as its core plugin, and registers a wrapper component around the rendered Player. + +```tsx +import { BasicReactDevtoolsPlugin } from "@player-devtools/basic-plugin-react"; + +const reactPlayer = new ReactPlayer({ + plugins: [new BasicReactDevtoolsPlugin()], +}); +``` + +### Constructor + +```ts +new BasicReactDevtoolsPlugin( + id?: string, // playerID; defaults to "default-id" + wrapper?: React.ComponentType, // custom per-Player wrapper +); +``` + +- **`id`** — the `playerID` this plugin instance reports. Give each Player a + distinct id when running more than one on a page so the clients (and the MCP + `select_player` / `invoke_action` tools) can address them individually. +- **`wrapper`** — overrides the default wrapper component. The default + (`BasicDevtoolsWrapper`) wraps the Player in a `
` and briefly + highlights it with a blue border when it becomes the selected Player. + +`DevtoolsWrapperProps` is `{ state: DevtoolsPluginsStore; playerID: string }` +plus `children`. + +### How it wires up + +`applyReact(reactPlayer)` bails when Devtools is inactive, calls +`super.applyReact` (which the base class uses to set up the messenger via +`useCommunicationLayer`), then taps `reactPlayer.hooks.webComponent` to wrap the +rendered component with a `DevtoolsContainer` that subscribes to the plugin store +and feeds the current state into the wrapper. + +> **NOTE** +> Whether Devtools is active is read from `localStorage` +> (`player-ui-devtools-active === "true"`), set by the browser extension popup. +> See [`@player-devtools/plugin-react`](../../plugin/react) for the base class +> details. + +## Native platform wrappers + +The Android, JVM, iOS, and SwiftUI packages are thin wrappers. They don't +reimplement instrumentation — they load the compiled `core` bundle +(`BasicDevtoolsPlugin.native.js`) into the platform's Player JS runtime and +adapt it to the platform's plugin protocol, exactly as `react` adapts `core` for +the web. Each is built with the corresponding [`rules_player`](../../../README.md) +macro (`kt_jvm`, `kt_android`, `ios_library`, `swiftui_plugin`). + +### JVM — `BasicDevtoolsPlugin` (Kotlin) + +`com.intuit.playerui.devtools.plugins.basic.BasicDevtoolsPlugin` extends the base +`DevtoolsPlugin` and is constructed through a `Runtime` extension that loads the +bundled core module via `ModuleLoader` and instantiates the JS plugin with +`Options(playerID, handler)`. + +```kotlin +val plugin = runtime.BasicDevtoolsPlugin( + BasicDevtoolsPlugin.Options(playerID = "my-player", handler = myHandler), +) +``` + +### Android — `BasicAndroidDevtoolsPlugin` + +Extends `AndroidDevtoolsPlugin`, building its core plugin +from the JVM package. Beyond the core behavior it taps `androidPlayer.hooks.context` +to apply a debug overlay style (`overlayStyle`, defaulting to +`R.style.BasicAndroidDevtoolsPlugin`). Like every wrapper it bails early when +`checkIfDevtoolsIsActive()` is false before calling `super.apply`. + +```kotlin +AndroidPlayer(BasicAndroidDevtoolsPlugin(id = "my-player"), /* ... */) +``` + +### iOS — `BaseBasicDevtoolsPlugin` (Swift) + +A `JSBasePlugin` conforming to `BaseDevtoolsPlugin`. It points the JS runtime at +the bundled `BasicDevtoolsPlugin.native.js`, polyfills the context, and passes +`DevtoolsPluginOptions(playerID:handler:)` into the core plugin. It supplies a +default no-op `Handler` — the core JS plugin provides the actual logging and +metadata. It does **not** own a Flipper connection; that's the SwiftUI layer's +job. It's `open` so the SwiftUI plugin can subclass it. + +### SwiftUI — `BasicDevtoolsPlugin` + +Subclasses `BaseBasicDevtoolsPlugin` and conforms to `DevtoolsPlugin`. This is the +plugin you add to a SwiftUI Player. It owns the runtime wiring the base class +can't: + +- holds the `DevtoolsFlipperPlugin` connection, +- keeps a strong reference to the `Messenger` so it isn't garbage-collected, +- tracks registered listener `UUID`s, and +- implements `deinit` to destroy the messenger and deregister listeners. + +```swift +let plugin = BasicDevtoolsPlugin(id: "my-player", flipperPlugin: devtoolsFlipperPlugin) +``` + +> **NOTE** +> If you write your own SwiftUI `DevtoolsPlugin`, you must implement `deinit` +> exactly like this one — the `DevtoolsPlugin` protocol can't provide it. + +## Enabling devtools + +Add `BasicReactDevtoolsPlugin` to your `ReactPlayer` configuration and activate a +client connection — the browser extension popup for web, or the `FlipperClient` +for mobile. See the [plugin authoring guide](../../plugin) for the platform +wiring and [the devtools root README](../../../README.md) for the overall +architecture. + +[browser extension]: https://github.com/player-ui/browser-devtools diff --git a/devtools/types/README.md b/devtools/types/README.md new file mode 100644 index 0000000..715e462 --- /dev/null +++ b/devtools/types/README.md @@ -0,0 +1,175 @@ +# Devtools Types + +The **Devtools Types** family is the shared type vocabulary for the Player UI +Devtools. Every other devtools package — the [plugins](../plugins), the +[client](../client), the [messenger](../messenger), the [Flipper plugin](../flipper-plugin), +and the [MCP server](../mcp) — speaks in the events and shapes defined here, so +the wire format between a running Player and a devtools client is described in +exactly one place. + +There is no runtime behavior in this family. It is **types only**: the TypeScript +package compiles to declarations, and the Swift package mirrors the same +contracts as protocols and structs so the iOS side can construct and decode the +identical messages. + +This directory contains **two** packages: + +| Package | Name / Product | Platform | Role | +| --- | --- | --- | --- | +| [`core`](#core--player-devtoolstypes) | `@player-devtools/types` | TypeScript | The canonical event + option type definitions. | +| [`ios`](#ios--playeruidevtoolstypes) | `PlayerUIDevtoolsTypes` | Swift | The Swift mirror — `BaseEvent` protocol + `InternalEvent` / `MessengerOptions`. | + +``` + @player-devtools/types (TS, canonical) + │ mirrored by + ▼ + PlayerUIDevtoolsTypes (Swift) + │ + ┌──────────────────────┼───────────────────────┐ + ▼ ▼ ▼ +messenger / plugins client / Flipper plugin MCP server +``` + +## Installation + +**Web / TypeScript** (npm): + +```bash +npm install @player-devtools/types +``` + +**iOS** (Swift Package Manager — a product of the `PlayerUIDevtools` package at +https://github.com/player-ui/player): + +```swift +.product(name: "PlayerUIDevtoolsTypes", package: "PlayerUIDevtools") +``` + +> Published versions are stamped at release time (sources here read +> `0.0.0-PLACEHOLDER`); pin the current release version when you add the +> dependency. + +## The `BaseEvent` / `ExtensionSupportedEvents` pattern + +Everything that travels between a Player and a devtools client is a +**`BaseEvent`** — a discriminated union member with a string `type`, a typed +`payload`, and an optional `target`: + +```ts +interface BaseEvent { + type: T; // the discriminant, e.g. "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE" + payload: P; + target?: string; +} +``` + +Concrete events are declared by fixing those generics — for example +`DevtoolsDataChangeEvent = BaseEvent<"PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE", DataChangePayload>`. +They are then collected into the **`ExtensionSupportedEvents`** union, which is +the complete set of messages a devtools client understands: + +``` +ExtensionSupportedEvents = + PlayerInitEvent | DevtoolsFlowChangeEvent | DevtoolsDataChangeEvent + | PlayerStoppedEvent | DevtoolsEventsBatchEvent + | ExtensionSelectedPlayerEvent | ExtensionSelectedPluginEvent + | BeaconEvent | DisconnectEvent + | DevtoolsPluginInteractionEvent | DevtoolsPluginSelectedPlayerEvent +``` + +This is the **extension point** of the whole system. To teach the devtools about +a new message, declare a `BaseEvent<...>` alias and add it to +`ExtensionSupportedEvents`; every consumer that is parameterized over the union +(the messenger, the store, the client) picks it up and stays exhaustively typed. +The [messenger](../messenger) is generic over `T extends BaseEvent` +precisely so it can carry whatever union you give it. + +## `core` — `@player-devtools/types` + +The canonical definitions. The public surface groups into three concerns. + +### Event vocabulary + +| Type | Purpose | +| --- | --- | +| `BaseEvent` | The base discriminated-union shape every event extends. | +| `MessengerEvent` | `T` (a domain event) **or** an `InternalEvent` — what actually flows over the wire. | +| `InternalEvent` | The messenger's own plumbing events (see below). | +| `BeaconEvent` | `MESSENGER_BEACON` — a heartbeat announcing a messenger exists. | +| `EventsBatchEvent` | `MESSENGER_EVENT_BATCH` — many events delivered at once. | +| `RequestLostEventsEvent` | `MESSENGER_REQUEST_LOST_EVENTS` — request a resend of missed events. | +| `DisconnectEvent` | `MESSENGER_DISCONNECT` — a messenger is going away. | +| `ExtensionSupportedEvents` | The full union of events a devtools client handles. | + +The `PLAYER_DEVTOOLS_*` domain events (`PlayerInitEvent`, +`DevtoolsFlowChangeEvent`, `DevtoolsDataChangeEvent`, `PlayerStoppedEvent`, +`ExtensionSelectedPlayerEvent`, `ExtensionSelectedPluginEvent`, +`DevtoolsPluginInteractionEvent`, `DevtoolsPluginSelectedPlayerEvent`) are all +`BaseEvent` aliases that flow into `ExtensionSupportedEvents`. + +### Transactions and messenger options + +| Type | Purpose | +| --- | --- | +| `TransactionMetadata` | The envelope a messenger stamps onto every message: `id`, `timestamp`, `sender`, `context`, `_messenger_`. | +| `Transaction` | `TransactionMetadata & MessengerEvent` — a fully-stamped message. | +| `MessengerOptions` | Configuration for a messenger instance: `sendMessage`, `addListener` / `removeListener`, `messageCallback`, `context`, `beaconIntervalMS`, `logger`, and friends. | +| `CommunicationLayerMethods` | `Pick, "sendMessage" \| "addListener" \| "removeListener">` — the minimal transport a client (or the [Flipper plugin](../flipper-plugin)) must supply. | +| `Connection` | Per-peer bookkeeping: messages sent/received and a `desync` flag. | + +`context` is always `"player" | "devtools"` — it identifies which side of the +connection a message originated from. + +### Plugin descriptors and store + +| Type | Purpose | +| --- | --- | +| `PluginData` | What a devtools plugin advertises at registration: `id`, `name`, `version`, `description`, its Devtools UI `flow`, and optional `capabilities`. | +| `PluginCapabilities` | The agent-visible descriptor: a `description`, the `data` keys the plugin publishes, and the `actions` it accepts. This is what the MCP [`describe_plugin`](../mcp) tool surfaces. | +| `ExtensionState` | The browser-extension view of the world: `current` player/plugin selection plus a `players` map of registered plugins, `active`, and `config`. | +| `DevtoolsPluginsStore` | The client-side store shape: `plugins`, accumulated `messages`, `interactions`, and `currentPlayer`. | + +> **NOTE** +> `PluginCapabilities` is the contract that makes plugins discoverable to agents. +> A plugin that declares its `data` keys and `actions` here can be driven by the +> [MCP server](../mcp) without anyone reading its source — keep it accurate. + +## `ios` — `PlayerUIDevtoolsTypes` + +The Swift mirror of `core`, built with the [`ios_library`](../../README.md) macro +(`PlayerUIDevtoolsTypes`). It depends on [`PlayerUIDevtoolsUtils`](../utils) for +the JS-bridging helpers. Three source files cover the same ground as the TS +package: + +- **`BaseEvent`** — a generic `protocol` with an `associatedtype Payload`, a + `type: String`, an optional `payload`, and an optional `target`. A protocol + extension supplies a nil `payload` default so type-only events (just a `type` + and `target`) need no payload boilerplate. This is the Swift analogue of + `BaseEvent`. +- **`InternalEvent`** — a concrete `BaseEvent` struct plus the + **`InternalEventType`** string enum (`MESSENGER_BEACON`, `MESSENGER_DISCONNECT`, + `MESSENGER_REQUEST_LOST_EVENTS`, `MESSENGER_EVENT_BATCH`, + `PLAYER_DEVTOOLS_PLUGIN_INTERACTION`). The raw values match the TS event + `type` strings exactly, so the two sides decode the same wire format. +- **`MessengerOptions`** — the Swift configuration object for a messenger. It + carries `id`, `context` (`MessengerContext` = `.player` / `.devtools`), + `logger`, `beaconIntervalMS`, `isDebug`, and the async `sendMessage` / + synchronous `addListener` / `removeListener` / `messageCallback` closures, and + exposes an `asJSValue` projection that hands those callbacks to the shared + `JSContext` running the core JS messenger. + +> **NOTE** +> The iOS package is type/contract parity for the Swift runtime — it does not +> re-implement messenger logic. The actual messaging runs in the JS core via the +> bridging in [`PlayerUIDevtoolsUtils`](../utils); `MessengerOptions.asJSValue` +> is the seam that adapts Swift closures into that JS context. + +## Related + +- [`../messenger`](../messenger) — the transport that carries these events. +- [`../plugins`](../plugins) — plugins that produce `PluginData` and emit the + `PLAYER_DEVTOOLS_*` events. +- [`../client`](../client) / [`../flipper-plugin`](../flipper-plugin) — consumers + that implement `CommunicationLayerMethods`. +- [`../mcp`](../mcp) — surfaces `PluginCapabilities` to agents. +- [`../README.md`](../../README.md) — the overall Devtools architecture. diff --git a/devtools/utils/README.md b/devtools/utils/README.md new file mode 100644 index 0000000..7e19928 --- /dev/null +++ b/devtools/utils/README.md @@ -0,0 +1,165 @@ +# Devtools Utils + +The **Devtools Utils** family holds the small, dependency-free building blocks +the rest of the Player UI Devtools lean on: a minimal observable store and a +reference-preserving deep-assign on the TypeScript side, and the JavaScriptCore +bridging + polyfills the native runtimes need to host the JS core. + +Nothing here is Player- or devtools-specific in its types — these are +general-purpose primitives — but they exist to serve the devtools, most notably +the immutable data-model updates the [plugins](../plugins) apply and the JS +runtime the [iOS plugins](../plugins) load. + +This directory contains **three** published packages (plus one private, +test-only package): + +| Package | Name / Product | Platform | Role | +| --- | --- | --- | --- | +| [`core`](#core--player-devtoolsutils) | `@player-devtools/utils` | TypeScript | `dsetAssign` (reference-preserving deep set) + a tiny `useStateReducer` store. | +| [`ios`](#ios--playeruidevtoolsutils) | `PlayerUIDevtoolsUtils` | Swift | JavaScriptCore bridging — construct/invoke JS classes safely from Swift. | +| [`swiftui`](#swiftui--playeruidevtoolsutilsswiftui) | `PlayerUIDevtoolsUtilsSwiftUI` | Swift | `setInterval` / `clearInterval` / `console` polyfills for the JS core. | + +``` +@player-devtools/utils (TS: dsetAssign, useStateReducer) + │ used by plugins/core for immutable data updates + ▼ + plugins / client / ... + +PlayerUIDevtoolsUtils (Swift: JSContext.construct, invokeMethodSafely) +PlayerUIDevtoolsUtilsSwiftUI (Swift: PolyfillPlugin) + │ used by the native devtools plugins to host the JS core + ▼ + plugins/ios, plugins/swiftui +``` + +## Installation + +**Web / TypeScript** (npm): + +```bash +npm install @player-devtools/utils +``` + +**iOS / SwiftUI** (Swift Package Manager — products of the `PlayerUIDevtools` +package at https://github.com/player-ui/player): + +```swift +.product(name: "PlayerUIDevtoolsUtils", package: "PlayerUIDevtools") // JSCore bridging +.product(name: "PlayerUIDevtoolsUtilsSwiftUI", package: "PlayerUIDevtools") // polyfills +``` + +> Published versions are stamped at release time (sources here read +> `0.0.0-PLACEHOLDER`); pin the current release version when you add the +> dependency. + +## `core` — `@player-devtools/utils` + +A handful of zero-dependency TypeScript primitives. + +### `dsetAssign` + +A deep-set that **preserves references** for branches it doesn't change. This is +the key utility behind the devtools' incremental data-model updates: when a +plugin re-publishes a Player's data, only the touched branches get new object +identities, so subscribers (and React) don't see spurious changes across the +whole tree. + +```ts +function dsetAssign( + obj: Record, + keys: Array, // path to the target location + value: V, + merge?: boolean, // default false +): void; +``` + +It walks `keys`, auto-vivifying intermediate objects, then deep-assigns `value` +into the existing slot in place rather than replacing it wholesale. The +deep-assign semantics (applied recursively) are: + +- **Objects** — recurse into each source key, reusing the existing nested object + so its reference and key insertion order survive. With `merge: false` + (default), keys present in the target but **absent** from the source are + **deleted**; with `merge: true` those stale keys are **kept**. +- **Arrays** — the target array is truncated/extended to the source length + (unless `merge: true`, which only adds), then each element is recursed into. +- **Primitives / type mismatch** — there's no identity to retain, so the source + value is assigned directly. + +> **NOTE** +> The API is inspired by [`dset`](https://github.com/lukeed/dset); the difference +> is that `dsetAssign` mutates the existing value in place to retain references, +> rather than spreading new objects along the path. The `merge` flag is the +> dial between "make the target exactly match the source" (delete) and "layer +> the source on top" (merge). The [basic plugin](../plugins/basic) pairs this +> with `immer` to apply data updates immutably. + +### `useStateReducer` + +A minimal, framework-agnostic observable store — `getState` / `subscribe` / +`dispatch` over a reducer, with `subscribe` firing immediately with the current +state and returning an unsubscribe. Dispatches that don't change the state +(reference-equal result) skip notifying subscribers. + +```ts +const store: Store = useStateReducer(reducer, initialState); +``` + +Also exported: the supporting types `Store`, `Reducer`, `Dispatch`, +`Subscriber`, `Subscribe`, and `Unsubscribe`. + +## `ios` — `PlayerUIDevtoolsUtils` + +Built with the [`ios_library`](../../README.md) macro (`PlayerUIDevtoolsUtils`), +this is the JavaScriptCore bridging layer the native devtools plugins use to +host the JS core. Its `JSContext` / `JSValue` extensions let Swift load and drive +a JS module: + +- **`JSContext.construct(className:inModule:fromFile:inBundle:withArguments:)`** — + evaluates a bundled `*.native.js` module, finds the exported class, constructs + it with the given args, and throws a descriptive `JSBaseError` if the file, + module, class, or construction fails (including surfacing JS exceptions). +- **`JSValue.invokeMethodSafely(_:withArguments:)`** — a guarded `invokeMethod` + that turns missing methods, `undefined`/`null` results, and thrown JS + exceptions into a `nil` return with a diagnostic log instead of a crash. + +> **NOTE** +> These extensions are public but intended for **use within Devtools only** — the +> source carries an explicit warning to that effect. They are the seam that lets +> the Swift devtools plugins reuse the shared JS instrumentation rather than +> re-implementing it natively. + +### `utils/ios/Resources/core` — internal/test-only + +`utils/ios/Resources/core` builds a private JS bundle (package name +`@player-devtools/utils-test-do-not-export`, declared `private = True`) that +`PlayerUIDevtoolsUtils` loads in its tests. It is **internal/test-only** and is +intentionally excluded from the released package — there is no consumer install +path for it, so it has no installation instructions. + +## `swiftui` — `PlayerUIDevtoolsUtilsSwiftUI` + +Built with the [`swiftui_plugin`](../../README.md) macro +(`PlayerUIDevtoolsUtilsSwiftUI`). It provides the browser APIs the JS core +expects but that a `JSBasePlugin` runtime does not have: + +- **`PolyfillPlugin`** — a `NativePlugin` that, on `apply`, installs polyfills + onto the Player's `JSContext`: `setInterval` / `clearInterval` (backed by a + shared timer manager) so the messenger's beacon scheduling works, and a + `console.log` / `console.error` shim that prints. Add it **before** any plugin + that depends on those APIs. + +> **NOTE** +> `PolyfillPlugin` is currently gated behind upstream Player fixes +> ([player-ui/player#772](https://github.com/player-ui/player/issues/772), +> [#773](https://github.com/player-ui/player/issues/773)) — see the `TODO`s in +> the source. The `JSContext.polyfill()` extension it wraps is the part in active +> use. + +## Related + +- [`../plugins`](../plugins) — primary consumer of `dsetAssign` (data updates) + and the native bridging/polyfills. +- [`../types`](../types) — `PlayerUIDevtoolsTypes` depends on + `PlayerUIDevtoolsUtils`. +- [`../README.md`](../../README.md) — the overall Devtools architecture. From 1f7ad1973b5c3e2cbdcc31b210e57335726d91ca Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Mon, 29 Jun 2026 06:50:08 -0700 Subject: [PATCH 5/5] Document profiler plugin, align group naming, add capabilities - Add a consolidated README for the profiler plugin family (core, content, react, jvm, android, ios, swiftui), following the basic-plugin template, and list it in the root README. - Make the Maven group / Kotlin package convention consistent with basic: move the profiler Kotlin packages from com.intuit.playerui.plugins.devtools.profiler to com.intuit.playerui.devtools.plugins.profiler, and derive the Maven group from GROUP via "%s.plugins" % GROUP in both BUILD files. Update the Android manifest package and add an explicit R import so the rename compiles. - Declare a capabilities block on ProfilerPluginData (data keys + start/stop/ reset-profiling actions) so the profiler is discoverable via the MCP describe_plugin tool. --- MODULE.bazel.lock | 2 +- README.md | 1 + devtools/plugins/profiler/README.md | 263 ++++++++++++++++++ devtools/plugins/profiler/android/BUILD | 5 +- .../android/src/main/AndroidManifest.xml | 2 +- .../profiler/ProfilerAndroidDevtoolsPlugin.kt | 5 +- .../plugins/profiler/content/src/index.ts | 32 ++- devtools/plugins/profiler/jvm/BUILD | 3 +- .../profiler/ProfilerDevtoolsPlugin.kt | 2 +- .../profiler/ProfilerDevtoolsPluginTest.kt | 4 +- 10 files changed, 308 insertions(+), 11 deletions(-) create mode 100644 devtools/plugins/profiler/README.md rename devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/{plugins/devtools => devtools/plugins}/profiler/ProfilerAndroidDevtoolsPlugin.kt (85%) rename devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/{plugins/devtools => devtools/plugins}/profiler/ProfilerDevtoolsPlugin.kt (97%) rename devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/{plugins/devtools => devtools/plugins}/profiler/ProfilerDevtoolsPluginTest.kt (95%) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index fa893df..4f63a19 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -442,7 +442,7 @@ "bzlTransitiveDigest": "aVqwKoRPrSXO367SJABlye04kmpR/9VM2xiXB3nh3Ls=", "usagesDigest": "qH5h0y49b/BYrI5SRoLkSpQctbXmqG8KSHgoA8eopCE=", "recordedFileInputs": { - "@@//package.json": "aee71511afd390a524a1dcc62cba1b2fc6974baad691f32139352437785ee55a" + "@@//package.json": "36d0e1380f22d8fa2cfa190537c0733e55b6ddb9d02e1927d3a83c599a018789" }, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/README.md b/README.md index 2e1f5ae..1d216f6 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ itself, using the [devtools-assets](https://github.com/player-ui/devtools-assets | ---------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | [`plugin`](./devtools/plugin) | TS · React · Android · JVM · iOS · SwiftUI | Base classes for building Devtools plugins. | | [`plugins/basic`](./devtools/plugins/basic) | all of the above | The standard plugin — exposes flow, data, logs, config; supports expression evaluation and flow overrides. The reference implementation. | +| [`plugins/profiler`](./devtools/plugins/profiler) | all of the above | Profiles Player hook execution timing and exposes it as a flame graph. | ### Clients (tooling side) diff --git a/devtools/plugins/profiler/README.md b/devtools/plugins/profiler/README.md new file mode 100644 index 0000000..07c3d47 --- /dev/null +++ b/devtools/plugins/profiler/README.md @@ -0,0 +1,263 @@ +# Profiler Devtools Plugin + +The **Profiler Devtools Plugin** instruments a running Player and records how long +its hooks take, surfacing the result to the Devtools clients (the +[browser extension], the [Flipper plugin](../../flipper-plugin), and the +[MCP server](../../mcp)) as a **flame graph**. It's the profiling counterpart to +the [Basic Devtools Plugin](../basic): where `basic` exposes flow / data / logs, +this one answers "where is time being spent?" + +It accepts three interactions from the clients — **start**, **stop**, and +**reset** profiling — and publishes the captured hook timings as a tree. + +This directory contains **seven** packages, the same layered shape as +[`basic`](../basic): a TypeScript `core` holds the profiling logic, `content` +defines the Devtools UI, and each platform — web, Android, JVM, iOS, SwiftUI — +has a thin wrapper that loads `core` into its runtime and configures the +messenger. + +**TypeScript (shared + web):** + +| Package | Name | Role | +| --- | --- | --- | +| [`core`](#core--player-devtoolsprofiler-plugin) | `@player-devtools/profiler-plugin` | The profiling instrumentation — intercepts Player hooks, builds the flame-graph tree, handles start/stop/reset. The other platforms load this bundle. | +| [`content`](#content--player-devtoolsprofiler-plugin-content) | `@player-devtools/profiler-plugin-content` | The Devtools UI flow (DSL content) plus the plugin's `id` and interaction constants. | +| [`react`](#react--player-devtoolsprofiler-plugin-react) | `@player-devtools/profiler-plugin-react` | React/web wrapper — binds the core plugin to `ReactPlayer`. | + +**Native platform wrappers** (each bundles `core`'s +`ProfilerDevtoolsPlugin.native.js` and wraps it, mirroring how `react` wraps +`core`): + +| Package | Target | Role | +| --- | --- | --- | +| [`jvm`](#native-platform-wrappers) | `kt_jvm` | `ProfilerDevtoolsPlugin` (Kotlin) — loads the core bundle into the Player JS runtime via `ModuleLoader`. | +| [`android`](#native-platform-wrappers) | `kt_android` | `ProfilerAndroidDevtoolsPlugin` — Android wrapper over `jvm`, adds the debug overlay styling. | +| [`ios`](#native-platform-wrappers) | `ios_library` | `BaseProfilerDevtoolsPlugin` (Swift) — `JSBasePlugin` that loads the core bundle. | +| [`swiftui`](#native-platform-wrappers) | `swiftui_plugin` | `ProfilerDevtoolsPlugin` (SwiftUI) — wraps the iOS base plugin and owns the Flipper connection + messenger lifecycle. | + +## Installation + +Install the package for the platform you're integrating. The web/TS packages +ship to npm; the Kotlin packages to Maven; the Swift packages via SPM as products +of the `PlayerUIDevtools` Swift package. + +**Web / TypeScript** (npm): + +```bash +npm install @player-devtools/profiler-plugin-react +# bring in @player-devtools/profiler-plugin / -content directly only if you wrap core yourself +``` + +**Android / JVM** (Maven — group `com.intuit.playerui.devtools.plugins`): + +```kotlin +// Android +implementation("com.intuit.playerui.devtools.plugins:profiler-android:") +// JVM +implementation("com.intuit.playerui.devtools.plugins:profiler-plugin:") +``` + +**iOS / SwiftUI** (Swift Package Manager — products of the `PlayerUIDevtools` +package at https://github.com/player-ui/player): + +```swift +.product(name: "PlayerUIDevtoolsProfilerPlugin", package: "PlayerUIDevtools") // SwiftUI +.product(name: "PlayerUIDevtoolsBaseProfilerDevtoolsPlugin", package: "PlayerUIDevtools") // iOS base +``` + +> Published versions are stamped at release time (sources here read +> `0.0.0-PLACEHOLDER`); pin the current release version when you add the +> dependency. + +## Architecture + +Like [`basic`](../basic), the Profiler follows the layered Devtools pattern: a +platform-agnostic **core** plugin owns all behavior, the **content** package +defines the Devtools UI, and a thin **platform** wrapper configures the messenger +and injects the core plugin into the host Player. + +``` +Player runtime + │ addProfilerInterceptorsToHooks: intercept every hook, record start/end timing + ▼ +ProfilerDevtoolsPlugin (core) ──genDataChangeTransaction──▶ messenger ──▶ Devtools client / MCP + ▲ │ + └──────── processInteraction (start / stop / reset) ◀──── PLUGIN_INTERACTION ─┘ +``` + +While profiling is active, hook timings accumulate into a node tree. On a +snapshot (and on stop), the tree is wrapped in a synthetic `root` node and +published to the `rootNode` (transformed flame-graph) and `rawNodes` data keys, +alongside `profiling` / `displayFlameGraph` flags that drive the UI. + +--- + +## `core` — `@player-devtools/profiler-plugin` + +`ProfilerDevtoolsPlugin` extends the base [`DevtoolsPlugin`](../../plugin) and is +constructed with everything except `pluginData` — it supplies its own +[`ProfilerPluginData`](#content--player-devtoolsprofiler-plugin-content). + +```ts +import { ProfilerDevtoolsPlugin } from "@player-devtools/profiler-plugin"; + +const plugin = new ProfilerDevtoolsPlugin({ playerID, handler }); +``` + +### How it profiles + +`apply(player)` bails when Devtools is inactive, then calls +`addProfilerInterceptorsToHooks(player, profiler)`. That walks the Player's +`hooks` tree recursively and attaches an `intercept` to every +[`tapable-ts`](https://github.com/intuit/hooks) hook it finds, recording the +start and end time of each tap (the `view` hook is skipped to avoid +double-counting the view controller's own hook). It then starts profiling +immediately. + +Captured timings are assembled into a `ProfilerNode` tree. On each snapshot the +tree is wrapped in a synthetic `root` node (whose `value` is the total span in +microseconds) and written into the store via `dset` + [`immer`](https://immerjs.github.io), +then dispatched as a data-change transaction. Published data keys: + +| Data key | Meaning | +| --- | --- | +| `rootNode` | The transformed flame-graph tree the UI renders. | +| `rawNodes` | The untransformed captured nodes. | +| `profiling` | Whether profiling is currently running. | +| `displayFlameGraph` | Whether the UI should show the graph (set on stop). | + +### Interactions it handles + +`processInteraction` calls `super.processInteraction`, then dispatches on type via +an interaction map: + +- **`start-profiling`** (`INTERACTIONS.START_PROFILING`) — starts the profiler + and flags `profiling: true`. +- **`stop-profiling`** (`INTERACTIONS.STOP_PROFILING`) — stops, publishes the + final `rootNode` / `rawNodes`, and flags `displayFlameGraph: true`. +- **`reset-profiling`** (`INTERACTIONS.RESET_PROFILING`) — clears the captured + nodes. + +--- + +## `content` — `@player-devtools/profiler-plugin-content` + +Defines what the Devtools clients render. It has no runtime Player dependency — +only `@player-devtools/types`. + +```ts +import { + ProfilerPluginData, + PLUGIN_ID, + INTERACTIONS, +} from "@player-devtools/profiler-plugin-content"; +``` + +- **`ProfilerPluginData`** — the `PluginData` descriptor: `id`, `name`, + `version`, the compiled Devtools UI `flow`, and a `capabilities` block. The + `capabilities` descriptor is what [`describe_plugin`](../../mcp) returns — it + documents the published `data` keys (`rootNode`, `rawNodes`, `profiling`, + `displayFlameGraph`) and the `start-profiling` / `stop-profiling` / + `reset-profiling` actions so agents can discover and drive the profiler. +- **`PLUGIN_ID`** — `"player-ui-profiler-plugin"`. +- **`INTERACTIONS`** — the interaction-type constants (`START_PROFILING`, + `STOP_PROFILING`, `RESET_PROFILING`). +- **`VIEWS_IDS`** — view ids used by the Devtools UI flow (`Profile`, `Raw`). + +The `version` is stamped from the build-time `__VERSION__` global (falling back +to `"unstamped"`), and the `flow` is generated from DSL into +`_generated/flow.json`. + +--- + +## `react` — `@player-devtools/profiler-plugin-react` + +`ProfilerReactDevtoolsPlugin` is the plugin you add to a **web** Player. It +extends [`ReactDevtoolsPlugin`](../../plugin/react) and constructs a +`ProfilerDevtoolsPlugin` as its core plugin. + +```tsx +import { ProfilerReactDevtoolsPlugin } from "@player-devtools/profiler-plugin-react"; + +const reactPlayer = new ReactPlayer({ + plugins: [new ProfilerReactDevtoolsPlugin()], +}); +``` + +### Constructor + +```ts +new ProfilerReactDevtoolsPlugin(id?: string); // playerID; defaults to "default-id" +``` + +Give each Player a distinct `id` when running more than one on a page so the +clients can address them individually. (Unlike +[`basic`](../basic#react--player-devtoolsbasic-plugin-react), the profiler +wrapper takes no custom wrapper component — the base +[`ReactDevtoolsPlugin`](../../plugin/react) handles messenger setup.) + +## Native platform wrappers + +The Android, JVM, iOS, and SwiftUI packages are thin wrappers — identical in +shape to [`basic`'s](../basic#native-platform-wrappers). They load the compiled +`core` bundle (`ProfilerDevtoolsPlugin.native.js`) into the platform's Player JS +runtime and adapt it to the platform's plugin protocol. + +### JVM — `ProfilerDevtoolsPlugin` (Kotlin) + +`com.intuit.playerui.devtools.plugins.profiler.ProfilerDevtoolsPlugin` extends the +base `DevtoolsPlugin`, constructed through a `Runtime` extension that loads the +bundled core module via `ModuleLoader` and instantiates the JS plugin with +`Options(playerID, handler)`. + +```kotlin +val plugin = runtime.ProfilerDevtoolsPlugin( + ProfilerDevtoolsPlugin.Options(playerID = "my-player", handler = myHandler), +) +``` + +### Android — `ProfilerAndroidDevtoolsPlugin` + +Extends `AndroidDevtoolsPlugin`, building its core plugin +from the JVM package. It taps `androidPlayer.hooks.context` to apply a debug +overlay style (`overlayStyle`, defaulting to +`R.style.ProfilerAndroidDevtoolsPlugin`), and bails early when +`checkIfDevtoolsIsActive()` is false. + +```kotlin +AndroidPlayer(ProfilerAndroidDevtoolsPlugin(id = "my-player"), /* ... */) +``` + +### iOS — `BaseProfilerDevtoolsPlugin` (Swift) + +A `JSBasePlugin` conforming to `BaseDevtoolsPlugin`. It points the JS runtime at +the bundled `ProfilerDevtoolsPlugin.native.js`, polyfills the context, and passes +`DevtoolsPluginOptions(playerID:handler:)` into the core plugin. It supplies a +default no-op `Handler` — the core JS plugin provides the actual behavior. It does +**not** own a Flipper connection; that's the SwiftUI layer's job. It's `open` so +the SwiftUI plugin can subclass it. + +### SwiftUI — `ProfilerDevtoolsPlugin` + +Subclasses `BaseProfilerDevtoolsPlugin` and conforms to `DevtoolsPlugin`. This is +the plugin you add to a SwiftUI Player. It owns the runtime wiring the base class +can't: the `DevtoolsFlipperPlugin` connection, a strong `Messenger` reference, the +registered listener `UUID`s, and a `deinit` that tears them down. + +```swift +let plugin = ProfilerDevtoolsPlugin(id: "my-player", flipperPlugin: devtoolsFlipperPlugin) +``` + +> **NOTE** +> If you write your own SwiftUI `DevtoolsPlugin`, you must implement `deinit` +> exactly like this one — the `DevtoolsPlugin` protocol can't provide it. + +## Enabling devtools + +Add the platform-specific Profiler plugin to your Player configuration and +activate a client connection — the browser extension popup for web, or the +`FlipperClient` for mobile. See the [plugin authoring guide](../../plugin) for the +platform wiring, [`basic`](../basic) for the sibling reference plugin, and +[the devtools root README](../../../README.md) for the overall architecture. + +[browser extension]: https://github.com/player-ui/browser-devtools diff --git a/devtools/plugins/profiler/android/BUILD b/devtools/plugins/profiler/android/BUILD index 9626e3c..45e5070 100644 --- a/devtools/plugins/profiler/android/BUILD +++ b/devtools/plugins/profiler/android/BUILD @@ -1,4 +1,5 @@ # Android lib that consumes jvm similar to how react consumes core +load("@build_constants//:constants.bzl", "GROUP") load("@rules_jvm_external//:defs.bzl", "artifact") load("//helpers:android.bzl", "kt_android") @@ -20,12 +21,12 @@ test_deps = [ kt_android( name = "profiler-android", - group = "com.intuit.playerui.plugins.devtools.profiler", + group = "%s.plugins" % GROUP, main_deps = main_deps, main_exports = main_exports, main_resources = main_resources, unit_test_deps = test_deps, - unit_test_package = "com.intuit.playerui.plugins.devtools.profiler", + unit_test_package = "com.intuit.playerui.devtools.plugins.profiler", ) alias( diff --git a/devtools/plugins/profiler/android/src/main/AndroidManifest.xml b/devtools/plugins/profiler/android/src/main/AndroidManifest.xml index d086f67..85a0e42 100644 --- a/devtools/plugins/profiler/android/src/main/AndroidManifest.xml +++ b/devtools/plugins/profiler/android/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - diff --git a/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt b/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerAndroidDevtoolsPlugin.kt similarity index 85% rename from devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt rename to devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerAndroidDevtoolsPlugin.kt index a9145dd..039ea51 100644 --- a/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt +++ b/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerAndroidDevtoolsPlugin.kt @@ -1,10 +1,11 @@ -package com.intuit.playerui.plugins.devtools.profiler +package com.intuit.playerui.devtools.plugins.profiler import androidx.annotation.StyleRes import com.intuit.playerui.android.AndroidPlayer import com.intuit.playerui.core.bridge.runtime.Runtime import com.intuit.playerui.devtools.AndroidDevtoolsPlugin -import com.intuit.playerui.plugins.devtools.profiler.ProfilerDevtoolsPlugin.Module.ProfilerDevtoolsPlugin +import com.intuit.playerui.devtools.plugins.profiler.ProfilerDevtoolsPlugin.Module.ProfilerDevtoolsPlugin +import com.intuit.playerui.devtools.plugins.profiler.R public class ProfilerAndroidDevtoolsPlugin( private val id: String, diff --git a/devtools/plugins/profiler/content/src/index.ts b/devtools/plugins/profiler/content/src/index.ts index e2f489c..c581283 100644 --- a/devtools/plugins/profiler/content/src/index.ts +++ b/devtools/plugins/profiler/content/src/index.ts @@ -1,5 +1,5 @@ import type { PluginData } from "@player-devtools/types"; -import { PLUGIN_ID } from "./constants"; +import { PLUGIN_ID, INTERACTIONS } from "./constants"; // Generated via dsl_compile target import flow from "../_generated/flow.json"; @@ -17,6 +17,36 @@ export const ProfilerPluginData: PluginData = { description: "Standard Player UI Profiler", version: PLUGIN_VERSION, flow, + capabilities: { + description: + "Profiles Player hook execution timing and exposes the result as a flame graph. Supports starting, stopping, and resetting profiling.", + data: { + rootNode: { + description: + "The captured hook timings as a flame-graph tree (transformed for display)", + }, + rawNodes: { + description: "The untransformed captured profiler nodes", + }, + profiling: { + description: "Whether profiling is currently running", + }, + displayFlameGraph: { + description: "Whether the client should render the flame graph", + }, + }, + actions: { + [INTERACTIONS.START_PROFILING]: { + description: "Start profiling Player hook execution", + }, + [INTERACTIONS.STOP_PROFILING]: { + description: "Stop profiling and publish the captured flame-graph data", + }, + [INTERACTIONS.RESET_PROFILING]: { + description: "Clear the captured profiler nodes", + }, + }, + }, }; export * from "./constants"; diff --git a/devtools/plugins/profiler/jvm/BUILD b/devtools/plugins/profiler/jvm/BUILD index db9ee47..2f4207b 100644 --- a/devtools/plugins/profiler/jvm/BUILD +++ b/devtools/plugins/profiler/jvm/BUILD @@ -1,3 +1,4 @@ +load("@build_constants//:constants.bzl", "GROUP") load("@rules_jvm_external//:defs.bzl", "artifact") load("//helpers:jvm.bzl", "kt_jvm") @@ -18,7 +19,7 @@ test_deps = [ kt_jvm( name = "profiler-plugin", - group = "com.intuit.playerui.plugins.devtools.profiler", + group = "%s.plugins" % GROUP, main_deps = main_deps, main_exports = main_exports, main_resources = main_resources, diff --git a/devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPlugin.kt b/devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPlugin.kt similarity index 97% rename from devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPlugin.kt rename to devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPlugin.kt index 5bd85a0..a75eeae 100644 --- a/devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPlugin.kt +++ b/devtools/plugins/profiler/jvm/src/main/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPlugin.kt @@ -1,4 +1,4 @@ -package com.intuit.playerui.plugins.devtools.profiler +package com.intuit.playerui.devtools.plugins.profiler import com.intuit.playerui.core.bridge.Node import com.intuit.playerui.core.bridge.runtime.Runtime diff --git a/devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPluginTest.kt b/devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPluginTest.kt similarity index 95% rename from devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPluginTest.kt rename to devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPluginTest.kt index 0a41565..5b7a829 100644 --- a/devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerDevtoolsPluginTest.kt +++ b/devtools/plugins/profiler/jvm/src/test/kotlin/com/intuit/playerui/devtools/plugins/profiler/ProfilerDevtoolsPluginTest.kt @@ -1,8 +1,8 @@ -package com.intuit.playerui.plugins.devtools.profiler +package com.intuit.playerui.devtools.plugins.profiler import com.intuit.playerui.devtools.DevtoolsHandler import com.intuit.playerui.devtools.DevtoolsPluginInteractionEvent -import com.intuit.playerui.plugins.devtools.profiler.ProfilerDevtoolsPlugin.Module.ProfilerDevtoolsPlugin +import com.intuit.playerui.devtools.plugins.profiler.ProfilerDevtoolsPlugin.Module.ProfilerDevtoolsPlugin import com.intuit.playerui.utils.test.RuntimeTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull