Claude Code & OpenClaw 环境一键同步工具 从 GitHub 仓库一键同步所有配置到本地 Claude Code 和 OpenClaw 环境。 功能概述 本 skill 提供 一键同步 功能,将配置从 GitHub 仓库同步到本地: - - 同步所有配置到本地 同步内容 | 组件 | 来源 | 目标 | 说明 | |------|------|------|------| | Output Styles | | | Claude Code 对话风格 | | CLAUDE.md | | | 全局提示词 | | MCP Config | | | MCP 服务器(合并) | | Agent Configs | | | OpenClaw Agent 配置 | | MCP Servers | | 集成到 | MCP 服务器独立配置 | | Plugins | | | OpenClaw 插件配置 | GitHub 仓库结构 配置目录说明 agents/ - Agent 配置 用于存放 OpenClaw Agent 的配置: 同步目标: mcp/ - MCP 服务器配置 用于存放 MCP 服务器的独立配置: 同步目标: 集成到 的 mcpServers plugins/ - 插件配置 用于存放 OpenClaw 插件配置: 同步目标: 使用方法 一、初始化 GitHub 仓…

,\n '%',\n '^',\n '&',\n '(',\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'I',\n 'J',\n 'K',\n 'L',\n 'M',\n 'N',\n 'O',\n 'P',\n 'Q',\n 'R',\n 'S',\n 'T',\n 'U',\n 'V',\n 'W',\n 'X',\n 'Y',\n 'Z',\n ':',\n '\u003c',\n '_',\n '>',\n '?',\n '~',\n '{',\n '|',\n '}',\n '\"',\n 'SoftLeft',\n 'SoftRight',\n 'Camera',\n 'Call',\n 'EndCall',\n 'VolumeDown',\n 'VolumeUp',\n]);\n\nfunction throwIfInvalidKey(key: string): KeyInput {\n if (validKeys.has(key)) {\n return key as KeyInput;\n }\n throw new Error(\n `${key} is invalid. Valid keys are: ${Array.from(validKeys.values()).join(',')}.`,\n );\n}\n\n/**\n * Returns the primary key, followed by modifiers in original order.\n */\nexport function parseKey(keyInput: string): [KeyInput, ...KeyInput[]] {\n let key = '';\n const result: KeyInput[] = [];\n for (const ch of keyInput) {\n // Handle cases like Shift++.\n if (ch === '+' && key) {\n result.push(throwIfInvalidKey(key));\n key = '';\n } else {\n key += ch;\n }\n }\n if (key) {\n result.push(throwIfInvalidKey(key));\n }\n\n if (result.length === 0) {\n throw new Error(`Key ${keyInput} could not be parsed.`);\n }\n\n if (new Set(result).size !== result.length) {\n throw new Error(`Key ${keyInput} contains duplicate keys.`);\n }\n\n return [result.at(-1), ...result.slice(0, -1)] as [KeyInput, ...KeyInput[]];\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":3825,"content_sha256":"7ffb098bbf548a7f2e7f4f28e1f3c408d5f518ee1db0663b3f40588821d224cd"},{"filename":"config/servers/src/utils/pagination.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {PaginationOptions} from './types.js';\n\nexport interface PaginationResult\u003cItem> {\n items: readonly Item[];\n currentPage: number;\n totalPages: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n startIndex: number;\n endIndex: number;\n invalidPage: boolean;\n}\n\nconst DEFAULT_PAGE_SIZE = 20;\n\nexport function paginate\u003cItem>(\n items: readonly Item[],\n options?: PaginationOptions,\n): PaginationResult\u003cItem> {\n const total = items.length;\n\n if (!options || noPaginationOptions(options)) {\n return {\n items,\n currentPage: 0,\n totalPages: 1,\n hasNextPage: false,\n hasPreviousPage: false,\n startIndex: 0,\n endIndex: total,\n invalidPage: false,\n };\n }\n\n const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n const {currentPage, invalidPage} = resolvePageIndex(\n options.pageIdx,\n totalPages,\n );\n\n const startIndex = currentPage * pageSize;\n const pageItems = items.slice(startIndex, startIndex + pageSize);\n const endIndex = startIndex + pageItems.length;\n\n return {\n items: pageItems,\n currentPage,\n totalPages,\n hasNextPage: currentPage \u003c totalPages - 1,\n hasPreviousPage: currentPage > 0,\n startIndex,\n endIndex,\n invalidPage,\n };\n}\n\nfunction noPaginationOptions(options: PaginationOptions): boolean {\n return options.pageSize === undefined && options.pageIdx === undefined;\n}\n\nfunction resolvePageIndex(\n pageIdx: number | undefined,\n totalPages: number,\n): {\n currentPage: number;\n invalidPage: boolean;\n} {\n if (pageIdx === undefined) {\n return {currentPage: 0, invalidPage: false};\n }\n\n if (pageIdx \u003c 0 || pageIdx >= totalPages) {\n return {currentPage: 0, invalidPage: true};\n }\n\n return {currentPage: pageIdx, invalidPage: false};\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":1920,"content_sha256":"31b93d36c4c45603b33655e02a5ba86200eb9ed6d5e11e091d34d52dc693ff91"},{"filename":"config/servers/src/utils/types.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nexport interface PaginationOptions {\n pageSize?: number;\n pageIdx?: number;\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":169,"content_sha256":"2ab4ffda18a12c041a902770e417675eae2e505ca18eb295ca58d67fd10f4b36"},{"filename":"config/servers/src/WaitForHelper.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {logger} from './logger.js';\nimport type {Page, Protocol, CdpPage} from './third_party/index.js';\n\nexport class WaitForHelper {\n #abortController = new AbortController();\n #page: CdpPage;\n #stableDomTimeout: number;\n #stableDomFor: number;\n #expectNavigationIn: number;\n #navigationTimeout: number;\n\n constructor(\n page: Page,\n cpuTimeoutMultiplier: number,\n networkTimeoutMultiplier: number,\n ) {\n this.#stableDomTimeout = 3000 * cpuTimeoutMultiplier;\n this.#stableDomFor = 100 * cpuTimeoutMultiplier;\n this.#expectNavigationIn = 100 * cpuTimeoutMultiplier;\n this.#navigationTimeout = 3000 * networkTimeoutMultiplier;\n this.#page = page as unknown as CdpPage;\n }\n\n /**\n * A wrapper that executes a action and waits for\n * a potential navigation, after which it waits\n * for the DOM to be stable before returning.\n */\n async waitForStableDom(): Promise\u003cvoid> {\n const stableDomObserver = await this.#page.evaluateHandle(timeout => {\n let timeoutId: ReturnType\u003ctypeof setTimeout>;\n function callback() {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n domObserver.resolver.resolve();\n domObserver.observer.disconnect();\n }, timeout);\n }\n const domObserver = {\n resolver: Promise.withResolvers\u003cvoid>(),\n observer: new MutationObserver(callback),\n };\n // It's possible that the DOM is not gonna change so we\n // need to start the timeout initially.\n callback();\n\n domObserver.observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n });\n\n return domObserver;\n }, this.#stableDomFor);\n\n this.#abortController.signal.addEventListener('abort', async () => {\n try {\n await stableDomObserver.evaluate(observer => {\n observer.observer.disconnect();\n observer.resolver.resolve();\n });\n await stableDomObserver.dispose();\n } catch {\n // Ignored cleanup errors\n }\n });\n\n return Promise.race([\n stableDomObserver.evaluate(async observer => {\n return await observer.resolver.promise;\n }),\n this.timeout(this.#stableDomTimeout).then(() => {\n throw new Error('Timeout');\n }),\n ]);\n }\n\n async waitForNavigationStarted() {\n // Currently Puppeteer does not have API\n // For when a navigation is about to start\n const navigationStartedPromise = new Promise\u003cboolean>(resolve => {\n const listener = (event: Protocol.Page.FrameStartedNavigatingEvent) => {\n if (\n [\n 'historySameDocument',\n 'historyDifferentDocument',\n 'sameDocument',\n ].includes(event.navigationType)\n ) {\n resolve(false);\n return;\n }\n\n resolve(true);\n };\n\n this.#page._client().on('Page.frameStartedNavigating', listener);\n this.#abortController.signal.addEventListener('abort', () => {\n resolve(false);\n this.#page._client().off('Page.frameStartedNavigating', listener);\n });\n });\n\n return await Promise.race([\n navigationStartedPromise,\n this.timeout(this.#expectNavigationIn).then(() => false),\n ]);\n }\n\n timeout(time: number): Promise\u003cvoid> {\n return new Promise\u003cvoid>(res => {\n const id = setTimeout(res, time);\n this.#abortController.signal.addEventListener('abort', () => {\n res();\n clearTimeout(id);\n });\n });\n }\n\n async waitForEventsAfterAction(\n action: () => Promise\u003cunknown>,\n ): Promise\u003cvoid> {\n const navigationFinished = this.waitForNavigationStarted()\n .then(navigationStated => {\n if (navigationStated) {\n return this.#page.waitForNavigation({\n timeout: this.#navigationTimeout,\n signal: this.#abortController.signal,\n });\n }\n return;\n })\n .catch(error => logger(error));\n\n try {\n await action();\n } catch (error) {\n // Clear up pending promises\n this.#abortController.abort();\n throw error;\n }\n\n try {\n await navigationFinished;\n\n // Wait for stable dom after navigation so we execute in\n // the correct context\n await this.waitForStableDom();\n } catch (error) {\n logger(error);\n } finally {\n this.#abortController.abort();\n }\n }\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":4479,"content_sha256":"10f7c484c99034b60781dc60778c0654ffefb0730c4eb9fd1831077feb68a0e7"},{"filename":"config/servers/tests/browser.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport os from 'node:os';\nimport path from 'node:path';\nimport {describe, it} from 'node:test';\n\nimport {executablePath} from 'puppeteer';\n\nimport {ensureBrowserConnected, launch} from '../src/browser.js';\n\ndescribe('browser', () => {\n it('cannot launch multiple times with the same profile', async () => {\n const tmpDir = os.tmpdir();\n const folderPath = path.join(tmpDir, `temp-folder-${crypto.randomUUID()}`);\n const browser1 = await launch({\n headless: true,\n isolated: false,\n userDataDir: folderPath,\n executablePath: executablePath(),\n devtools: false,\n });\n try {\n try {\n const browser2 = await launch({\n headless: true,\n isolated: false,\n userDataDir: folderPath,\n executablePath: executablePath(),\n devtools: false,\n });\n await browser2.close();\n assert.fail('not reached');\n } catch (err) {\n assert.strictEqual(\n err.message,\n `The browser is already running for ${folderPath}. Use --isolated to run multiple browser instances.`,\n );\n }\n } finally {\n await browser1.close();\n }\n });\n\n it('launches with the initial viewport', async () => {\n const tmpDir = os.tmpdir();\n const folderPath = path.join(tmpDir, `temp-folder-${crypto.randomUUID()}`);\n const browser = await launch({\n headless: true,\n isolated: false,\n userDataDir: folderPath,\n executablePath: executablePath(),\n viewport: {\n width: 1501,\n height: 801,\n },\n devtools: false,\n });\n try {\n const [page] = await browser.pages();\n const result = await page.evaluate(() => {\n return {width: window.innerWidth, height: window.innerHeight};\n });\n assert.deepStrictEqual(result, {\n width: 1501,\n height: 801,\n });\n } finally {\n await browser.close();\n }\n });\n it('connects to an existing browser with userDataDir', async () => {\n const tmpDir = os.tmpdir();\n const folderPath = path.join(tmpDir, `temp-folder-${crypto.randomUUID()}`);\n const browser = await launch({\n headless: true,\n isolated: false,\n userDataDir: folderPath,\n executablePath: executablePath(),\n devtools: false,\n args: ['--remote-debugging-port=0'],\n });\n try {\n const connectedBrowser = await ensureBrowserConnected({\n userDataDir: folderPath,\n devtools: false,\n });\n assert.ok(connectedBrowser);\n assert.ok(connectedBrowser.connected);\n connectedBrowser.disconnect();\n } finally {\n await browser.close();\n }\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":2769,"content_sha256":"73ce8a4f2bfbd4cb3641ff28ef3e63b459b7ff02aafe509deef3bba31a90fed5"},{"filename":"config/servers/tests/cli.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {parseArguments} from '../src/cli.js';\n\ndescribe('cli args parsing', () => {\n const defaultArgs = {\n 'category-emulation': true,\n categoryEmulation: true,\n 'category-performance': true,\n categoryPerformance: true,\n 'category-network': true,\n categoryNetwork: true,\n 'auto-connect': undefined,\n autoConnect: undefined,\n };\n\n it('parses with default args', async () => {\n const args = parseArguments('1.0.0', ['node', 'main.js']);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n });\n });\n\n it('parses with browser url', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--browserUrl',\n 'http://localhost:3000',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n 'browser-url': 'http://localhost:3000',\n browserUrl: 'http://localhost:3000',\n u: 'http://localhost:3000',\n });\n });\n\n it('parses with user data dir', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--user-data-dir',\n '/tmp/chrome-profile',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n 'user-data-dir': '/tmp/chrome-profile',\n userDataDir: '/tmp/chrome-profile',\n });\n });\n\n it('parses an empty browser url', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--browserUrl',\n '',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n 'browser-url': undefined,\n browserUrl: undefined,\n u: undefined,\n channel: 'stable',\n });\n });\n\n it('parses with executable path', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--executablePath',\n '/tmp/test 123/chrome',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n 'executable-path': '/tmp/test 123/chrome',\n e: '/tmp/test 123/chrome',\n executablePath: '/tmp/test 123/chrome',\n });\n });\n\n it('parses viewport', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--viewport',\n '888x777',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n viewport: {\n width: 888,\n height: 777,\n },\n });\n });\n\n it('parses viewport', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n `--chrome-arg='--no-sandbox'`,\n `--chrome-arg='--disable-setuid-sandbox'`,\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n 'chrome-arg': ['--no-sandbox', '--disable-setuid-sandbox'],\n chromeArg: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n });\n\n it('parses wsEndpoint with ws:// protocol', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--wsEndpoint',\n 'ws://127.0.0.1:9222/devtools/browser/abc123',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n 'ws-endpoint': 'ws://127.0.0.1:9222/devtools/browser/abc123',\n wsEndpoint: 'ws://127.0.0.1:9222/devtools/browser/abc123',\n w: 'ws://127.0.0.1:9222/devtools/browser/abc123',\n });\n });\n\n it('parses wsEndpoint with wss:// protocol', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--wsEndpoint',\n 'wss://example.com:9222/devtools/browser/abc123',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n 'ws-endpoint': 'wss://example.com:9222/devtools/browser/abc123',\n wsEndpoint: 'wss://example.com:9222/devtools/browser/abc123',\n w: 'wss://example.com:9222/devtools/browser/abc123',\n });\n });\n\n it('parses wsHeaders with valid JSON', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--wsEndpoint',\n 'ws://127.0.0.1:9222/devtools/browser/abc123',\n '--wsHeaders',\n '{\"Authorization\":\"Bearer token\",\"X-Custom\":\"value\"}',\n ]);\n assert.deepStrictEqual(args.wsHeaders, {\n Authorization: 'Bearer token',\n 'X-Custom': 'value',\n });\n });\n\n it('parses disabled category', async () => {\n const args = parseArguments('1.0.0', [\n 'node',\n 'main.js',\n '--no-category-emulation',\n ]);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n 'category-emulation': false,\n categoryEmulation: false,\n });\n });\n it('parses auto-connect', async () => {\n const args = parseArguments('1.0.0', ['node', 'main.js', '--auto-connect']);\n assert.deepStrictEqual(args, {\n ...defaultArgs,\n _: [],\n headless: false,\n $0: 'npx chrome-devtools-mcp@latest',\n channel: 'stable',\n 'auto-connect': true,\n autoConnect: true,\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":5824,"content_sha256":"0b6e7621d61cf9ccc982e128805cd5a88a4883d46f011ddc23f58d0224a01796"},{"filename":"config/servers/tests/DevtoolsUtils.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {afterEach, describe, it} from 'node:test';\n\nimport sinon from 'sinon';\n\nimport {\n extractUrlLikeFromDevToolsTitle,\n urlsEqual,\n mapIssueToMessageObject,\n UniverseManager,\n} from '../src/DevtoolsUtils.js';\nimport {ISSUE_UTILS} from '../src/issue-descriptions.js';\nimport {DevTools} from '../src/third_party/index.js';\nimport type {Browser, Target} from '../src/third_party/index.js';\n\nimport {\n getMockBrowser,\n getMockPage,\n mockListener,\n withBrowser,\n} from './utils.js';\n\ndescribe('extractUrlFromDevToolsTitle', () => {\n it('deals with no trailing /', () => {\n assert.strictEqual(\n extractUrlLikeFromDevToolsTitle('DevTools - example.com'),\n 'example.com',\n );\n });\n it('deals with a trailing /', () => {\n assert.strictEqual(\n extractUrlLikeFromDevToolsTitle('DevTools - example.com/'),\n 'example.com/',\n );\n });\n it('deals with www', () => {\n assert.strictEqual(\n extractUrlLikeFromDevToolsTitle('DevTools - www.example.com/'),\n 'www.example.com/',\n );\n });\n it('deals with complex url', () => {\n assert.strictEqual(\n extractUrlLikeFromDevToolsTitle(\n 'DevTools - www.example.com/path.html?a=b#3',\n ),\n 'www.example.com/path.html?a=b#3',\n );\n });\n});\n\ndescribe('urlsEqual', () => {\n it('ignores trailing slashes', () => {\n assert.strictEqual(\n urlsEqual('https://google.com/', 'https://google.com'),\n true,\n );\n });\n\n it('ignores www', () => {\n assert.strictEqual(\n urlsEqual('https://google.com/', 'https://www.google.com'),\n true,\n );\n });\n\n it('ignores protocols', () => {\n assert.strictEqual(\n urlsEqual('https://google.com/', 'http://www.google.com'),\n true,\n );\n });\n\n it('does not ignore other subdomains', () => {\n assert.strictEqual(\n urlsEqual('https://google.com/', 'https://photos.google.com'),\n false,\n );\n });\n\n it('ignores hash', () => {\n assert.strictEqual(\n urlsEqual('https://google.com/#', 'http://www.google.com'),\n true,\n );\n assert.strictEqual(\n urlsEqual('https://google.com/#21', 'http://www.google.com#12'),\n true,\n );\n });\n});\n\ndescribe('mapIssueToMessageObject', () => {\n const mockDescription = {\n file: 'mock-issue.md',\n substitutions: new Map([['PLACEHOLDER_VALUE', 'substitution value']]),\n links: [\n {link: 'http://example.com/learnmore', linkTitle: 'Learn more'},\n {\n link: 'http://example.com/another-learnmore',\n linkTitle: 'Learn more 2',\n },\n ],\n };\n\n afterEach(() => {\n sinon.restore();\n });\n\n it('maps aggregated issue with substituted description', () => {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getDescription.returns(mockDescription);\n mockAggregatedIssue.getAggregatedIssuesCount.returns(1);\n\n const getIssueDescriptionStub = sinon.stub(\n ISSUE_UTILS,\n 'getIssueDescription',\n );\n\n getIssueDescriptionStub\n .withArgs('mock-issue.md')\n .returns(\n '# Mock Issue Title\\n\\nThis is a mock issue description with a {PLACEHOLDER_VALUE}.',\n );\n\n const result = mapIssueToMessageObject(mockAggregatedIssue);\n const expected = {\n type: 'issue',\n item: mockAggregatedIssue,\n message: 'Mock Issue Title',\n count: 1,\n description:\n '# Mock Issue Title\\n\\nThis is a mock issue description with a substitution value.',\n };\n assert.deepStrictEqual(result, expected);\n });\n\n it('returns null for the issue with no description', () => {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getDescription.returns(null);\n\n const result = mapIssueToMessageObject(mockAggregatedIssue);\n assert.equal(result, null);\n });\n\n it('returns null if there is no desciption file', () => {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getDescription.returns(mockDescription);\n mockAggregatedIssue.getAggregatedIssuesCount.returns(1);\n\n const getIssueDescriptionStub = sinon.stub(\n ISSUE_UTILS,\n 'getIssueDescription',\n );\n\n getIssueDescriptionStub.withArgs('mock-issue.md').returns(null);\n const result = mapIssueToMessageObject(mockAggregatedIssue);\n assert.equal(result, null);\n });\n\n it(\"returns null if can't parse the title\", () => {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getDescription.returns(mockDescription);\n mockAggregatedIssue.getAggregatedIssuesCount.returns(1);\n\n const getIssueDescriptionStub = sinon.stub(\n ISSUE_UTILS,\n 'getIssueDescription',\n );\n\n getIssueDescriptionStub\n .withArgs('mock-issue.md')\n .returns('No title test {PLACEHOLDER_VALUE}');\n assert.deepStrictEqual(mapIssueToMessageObject(mockAggregatedIssue), null);\n });\n\n it('returns null if devtools utill function throws an error', () => {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getDescription.returns(mockDescription);\n mockAggregatedIssue.getAggregatedIssuesCount.returns(1);\n\n const getIssueDescriptionStub = sinon.stub(\n ISSUE_UTILS,\n 'getIssueDescription',\n );\n // An error will be thrown if placeholder doesn't start from PLACEHOLDER_\n getIssueDescriptionStub\n .withArgs('mock-issue.md')\n .returns('No title test {WRONG_PLACEHOLDER}');\n assert.deepStrictEqual(mapIssueToMessageObject(mockAggregatedIssue), null);\n });\n});\n\ndescribe('UniverseManager', () => {\n it('calls the factory for existing pages', async () => {\n const browser = getMockBrowser();\n const factory = sinon.stub().resolves({});\n const manager = new UniverseManager(browser, factory);\n await manager.init(await browser.pages());\n\n const page = (await browser.pages())[0];\n sinon.assert.calledOnceWithExactly(factory, page);\n });\n\n it('calls the factory only once for the same page', async () => {\n const browser = {\n ...mockListener(),\n } as unknown as Browser;\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const factory = sinon.stub().returns(new Promise(() => {})); // Don't resolve.\n const manager = new UniverseManager(browser, factory);\n await manager.init([]);\n\n sinon.assert.notCalled(factory);\n\n const page = getMockPage();\n browser.emit('targetcreated', {\n page: () => Promise.resolve(page),\n } as Target);\n browser.emit('targetcreated', {\n page: () => Promise.resolve(page),\n } as Target);\n\n await new Promise(r => setTimeout(r, 0)); // One event loop tick for the micro task queue to run.\n\n sinon.assert.calledOnceWithExactly(factory, page);\n });\n\n it('works with a real browser', async () => {\n await withBrowser(async (browser, page) => {\n const manager = new UniverseManager(browser);\n await manager.init([page]);\n\n assert.notStrictEqual(manager.get(page), null);\n });\n });\n\n it('ignores pauses', async () => {\n await withBrowser(async (browser, page) => {\n const manager = new UniverseManager(browser);\n await manager.init([page]);\n const targetUniverse = manager.get(page);\n assert.ok(targetUniverse);\n const model = targetUniverse.target.model(DevTools.DebuggerModel);\n assert.ok(model);\n\n const pausedSpy = sinon.stub();\n model.addEventListener('DebuggerPaused' as any, pausedSpy); // eslint-disable-line\n\n const result = await page.evaluate('debugger; 1 + 1');\n assert.strictEqual(result, 2);\n\n sinon.assert.notCalled(pausedSpy);\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":7892,"content_sha256":"4fe89aaeda85bec39968586bb33eed6649e853232bfb81abb4c98311fb0552ac"},{"filename":"config/servers/tests/formatters/consoleFormatter.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {describe, it} from 'node:test';\n\nimport type {ConsoleMessageData} from '../../src/formatters/consoleFormatter.js';\nimport {\n formatConsoleEventShort,\n formatConsoleEventVerbose,\n} from '../../src/formatters/consoleFormatter.js';\nimport {getMockAggregatedIssue} from '../utils.js';\n\ndescribe('consoleFormatter', () => {\n describe('formatConsoleEventShort', () => {\n it('formats a console.log message', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 1,\n type: 'log',\n message: 'Hello, world!',\n args: [],\n };\n const result = formatConsoleEventShort(message);\n t.assert.snapshot?.(result);\n });\n\n it('formats a console.log message with one argument', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 2,\n type: 'log',\n message: 'Processing file:',\n args: ['file.txt'],\n };\n const result = formatConsoleEventShort(message);\n t.assert.snapshot?.(result);\n });\n\n it('formats a console.log message with multiple arguments', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 3,\n type: 'log',\n message: 'Processing file:',\n args: ['file.txt', 'another file'],\n };\n const result = formatConsoleEventShort(message);\n t.assert.snapshot?.(result);\n });\n });\n\n describe('formatConsoleEventVerbose', () => {\n it('formats a console.log message', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 1,\n type: 'log',\n message: 'Hello, world!',\n args: [],\n };\n const result = formatConsoleEventVerbose(message);\n t.assert.snapshot?.(result);\n });\n\n it('formats a console.log message with one argument', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 2,\n type: 'log',\n message: 'Processing file:',\n args: ['file.txt'],\n };\n const result = formatConsoleEventVerbose(message);\n t.assert.snapshot?.(result);\n });\n\n it('formats a console.log message with multiple arguments', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 3,\n type: 'log',\n message: 'Processing file:',\n args: ['file.txt', 'another file'],\n };\n const result = formatConsoleEventVerbose(message);\n t.assert.snapshot?.(result);\n });\n\n it('formats a console.error message', t => {\n const message: ConsoleMessageData = {\n consoleMessageStableId: 4,\n type: 'error',\n message: 'Something went wrong',\n };\n const result = formatConsoleEventVerbose(message);\n t.assert.snapshot?.(result);\n });\n });\n\n it('formats a console.log message with issue type', t => {\n const testGenericIssue = {\n details: () => {\n return {\n violatingNodeId: 2,\n violatingNodeAttribute: 'test',\n };\n },\n };\n const mockAggregatedIssue = getMockAggregatedIssue();\n const mockDescription = {\n file: 'mock.md',\n links: [\n {link: 'http://example.com/learnmore', linkTitle: 'Learn more'},\n {\n link: 'http://example.com/another-learnmore',\n linkTitle: 'Learn more 2',\n },\n ],\n };\n mockAggregatedIssue.getDescription.returns(mockDescription);\n // @ts-expect-error generic issue stub bypass\n mockAggregatedIssue.getGenericIssues.returns(new Set([testGenericIssue]));\n const mockDescriptionFileContent =\n '# Mock Issue Title\\n\\nThis is a mock issue description';\n\n const message: ConsoleMessageData = {\n consoleMessageStableId: 5,\n type: 'issue',\n description: mockDescriptionFileContent,\n item: mockAggregatedIssue,\n };\n\n const result = formatConsoleEventVerbose(message);\n t.assert.snapshot?.(result);\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":3982,"content_sha256":"b3d5b8744f7dd23ffe800e97347e8d5773ad89c25df93e195ea61fcca34cfb5d"},{"filename":"config/servers/tests/formatters/networkFormatter.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {ProtocolError} from 'puppeteer-core';\n\nimport {\n getFormattedHeaderValue,\n getFormattedRequestBody,\n getFormattedResponseBody,\n getShortDescriptionForRequest,\n} from '../../src/formatters/networkFormatter.js';\nimport {getMockRequest, getMockResponse} from '../utils.js';\n\ndescribe('networkFormatter', () => {\n describe('getShortDescriptionForRequest', () => {\n it('works', async () => {\n const request = getMockRequest();\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(result, 'reqid=1 GET http://example.com [pending]');\n });\n it('shows correct method', async () => {\n const request = getMockRequest({method: 'POST'});\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(result, 'reqid=1 POST http://example.com [pending]');\n });\n it('shows correct status for request with response code in 200', async () => {\n const response = getMockResponse();\n const request = getMockRequest({response});\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(result, 'reqid=1 GET http://example.com [success - 200]');\n });\n it('shows correct status for request with response code in 100', async () => {\n const response = getMockResponse({\n status: 199,\n });\n const request = getMockRequest({response});\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(result, 'reqid=1 GET http://example.com [failed - 199]');\n });\n it('shows correct status for request with response code above 200', async () => {\n const response = getMockResponse({\n status: 300,\n });\n const request = getMockRequest({response});\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(result, 'reqid=1 GET http://example.com [failed - 300]');\n });\n it('shows correct status for request that failed', async () => {\n const request = getMockRequest({\n failure() {\n return {\n errorText: 'Error in Network',\n };\n },\n });\n const result = getShortDescriptionForRequest(request, 1);\n\n assert.equal(\n result,\n 'reqid=1 GET http://example.com [failed - Error in Network]',\n );\n });\n\n it('marks requests selected in DevTools UI', async () => {\n const request = getMockRequest();\n const result = getShortDescriptionForRequest(request, 1, true);\n\n assert.equal(\n result,\n 'reqid=1 GET http://example.com [pending] [selected in the DevTools Network panel]',\n );\n });\n });\n\n describe('getFormattedHeaderValue', () => {\n it('works', () => {\n const result = getFormattedHeaderValue({\n key: 'value',\n });\n\n assert.deepEqual(result, ['- key:value']);\n });\n it('with multiple', () => {\n const result = getFormattedHeaderValue({\n key: 'value',\n key2: 'value2',\n key3: 'value3',\n key4: 'value4',\n });\n\n assert.deepEqual(result, [\n '- key:value',\n '- key2:value2',\n '- key3:value3',\n '- key4:value4',\n ]);\n });\n it('with non', () => {\n const result = getFormattedHeaderValue({});\n\n assert.deepEqual(result, []);\n });\n });\n\n describe('getFormattedRequestBody', () => {\n it('shows data from fetchPostData if postData is undefined', async () => {\n const request = getMockRequest({\n hasPostData: true,\n postData: undefined,\n fetchPostData: Promise.resolve('test'),\n });\n\n const result = await getFormattedRequestBody(request, 200);\n\n assert.strictEqual(result, 'test');\n });\n it('shows not available when no postData available', async () => {\n const request = getMockRequest({\n hasPostData: false,\n });\n\n const result = await getFormattedRequestBody(request, 200);\n\n assert.strictEqual(result, undefined);\n });\n it('shows request body when postData is available', async () => {\n const request = getMockRequest({\n postData: JSON.stringify({\n request: 'body',\n }),\n hasPostData: true,\n });\n\n const result = await getFormattedRequestBody(request, 200);\n\n assert.strictEqual(\n result,\n `${JSON.stringify({\n request: 'body',\n })}`,\n );\n });\n it('shows trunkated string correctly with postData', async () => {\n const request = getMockRequest({\n postData: 'some text that is longer than expected',\n hasPostData: true,\n });\n\n const result = await getFormattedRequestBody(request, 20);\n\n assert.strictEqual(result, 'some text that is lo... \u003ctruncated>');\n });\n it('shows trunkated string correctly with fetchPostData', async () => {\n const request = getMockRequest({\n fetchPostData: Promise.resolve(\n 'some text that is longer than expected',\n ),\n postData: undefined,\n hasPostData: true,\n });\n\n const result = await getFormattedRequestBody(request, 20);\n\n assert.strictEqual(result, 'some text that is lo... \u003ctruncated>');\n });\n it('shows not available on exception', async () => {\n const request = getMockRequest({\n hasPostData: true,\n postData: undefined,\n fetchPostData: Promise.reject(new ProtocolError()),\n });\n\n const result = await getFormattedRequestBody(request, 200);\n\n assert.strictEqual(result, '\u003cnot available anymore>');\n });\n });\n\n describe('getFormattedResponseBody', () => {\n it('handles empty buffer correctly', async () => {\n const response = getMockResponse();\n response.buffer = () => {\n return Promise.resolve(Buffer.from(''));\n };\n\n const result = await getFormattedResponseBody(response, 200);\n\n assert.strictEqual(result, '\u003cempty response>');\n });\n it('handles base64 text correctly', async () => {\n const binaryBuffer = Buffer.from([\n 0xde, 0xad, 0xbe, 0xef, 0x00, 0x41, 0x42, 0x43,\n ]);\n const response = getMockResponse();\n response.buffer = () => {\n return Promise.resolve(binaryBuffer);\n };\n\n const result = await getFormattedResponseBody(response, 200);\n\n assert.strictEqual(result, '\u003cbinary data>');\n });\n it('handles the text limit correctly', async () => {\n const response = getMockResponse();\n response.buffer = () => {\n return Promise.resolve(\n Buffer.from('some text that is longer than expected'),\n );\n };\n\n const result = await getFormattedResponseBody(response, 20);\n\n assert.strictEqual(result, 'some text that is lo... \u003ctruncated>');\n });\n it('handles the text format correctly', async () => {\n const response = getMockResponse();\n response.buffer = () => {\n return Promise.resolve(Buffer.from(JSON.stringify({response: 'body'})));\n };\n\n const result = await getFormattedResponseBody(response, 200);\n\n assert.strictEqual(result, `${JSON.stringify({response: 'body'})}`);\n });\n it('handles error correctly', async () => {\n const response = getMockResponse();\n response.buffer = () => {\n // CDP Error simulation\n return Promise.reject(new ProtocolError());\n };\n\n const result = await getFormattedResponseBody(response, 200);\n\n assert.strictEqual(result, '\u003cnot available anymore>');\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":7603,"content_sha256":"996ce2e9c9ea5db7437a4ace788c80e5bbb0ff9dfda9be973e8a56ee2540e64a"},{"filename":"config/servers/tests/formatters/snapshotFormatter.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport type {ElementHandle} from 'puppeteer-core';\n\nimport {formatSnapshotNode} from '../../src/formatters/snapshotFormatter.js';\nimport type {TextSnapshotNode} from '../../src/McpContext.js';\n\ndescribe('snapshotFormatter', () => {\n it('formats a snapshot with value properties', () => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'textbox',\n name: 'textbox',\n value: 'value',\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node);\n assert.strictEqual(\n formatted,\n `uid=1_1 textbox \"textbox\" value=\"value\"\n uid=1_2 statictext \"text\"\n`,\n );\n });\n\n it('formats a snapshot with boolean properties', () => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'button',\n name: 'button',\n disabled: true,\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node);\n assert.strictEqual(\n formatted,\n `uid=1_1 button \"button\" disableable disabled\n uid=1_2 statictext \"text\"\n`,\n );\n });\n\n it('formats a snapshot with checked properties', () => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'checkbox',\n name: 'checkbox',\n checked: true,\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node);\n assert.strictEqual(\n formatted,\n `uid=1_1 checkbox \"checkbox\" checked\n uid=1_2 statictext \"text\"\n`,\n );\n });\n\n it('formats a snapshot with multiple different type attributes', () => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'root',\n name: 'root',\n children: [\n {\n id: '1_2',\n role: 'button',\n name: 'button',\n focused: true,\n disabled: true,\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n {\n id: '1_3',\n role: 'textbox',\n name: 'textbox',\n value: 'value',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node);\n assert.strictEqual(\n formatted,\n `uid=1_1 root \"root\"\n uid=1_2 button \"button\" disableable disabled focusable focused\n uid=1_3 textbox \"textbox\" value=\"value\"\n`,\n );\n });\n\n it('formats with DevTools data not included into a snapshot', t => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'checkbox',\n name: 'checkbox',\n checked: true,\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node, {\n snapshotId: '1',\n root: node,\n idToNode: new Map(),\n hasSelectedElement: true,\n verbose: false,\n });\n\n t.assert.snapshot?.(formatted);\n });\n\n it('does not include a note if the snapshot is already verbose', t => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'checkbox',\n name: 'checkbox',\n checked: true,\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node, {\n snapshotId: '1',\n root: node,\n idToNode: new Map(),\n hasSelectedElement: true,\n verbose: true,\n });\n\n t.assert.snapshot?.(formatted);\n });\n\n it('formats with DevTools data included into a snapshot', t => {\n const node: TextSnapshotNode = {\n id: '1_1',\n role: 'checkbox',\n name: 'checkbox',\n checked: true,\n children: [\n {\n id: '1_2',\n role: 'statictext',\n name: 'text',\n children: [],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n },\n ],\n elementHandle: async (): Promise\u003cElementHandle\u003cElement> | null> => {\n return null;\n },\n };\n\n const formatted = formatSnapshotNode(node, {\n snapshotId: '1',\n root: node,\n idToNode: new Map(),\n hasSelectedElement: true,\n selectedElementUid: '1_1',\n verbose: false,\n });\n\n t.assert.snapshot?.(formatted);\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":6125,"content_sha256":"4e998d992faea655f3fc63f0e5ec00768504f74b20dd7b67a9a12233aae976ef"},{"filename":"config/servers/tests/index.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport fs from 'node:fs';\nimport {describe, it} from 'node:test';\n\nimport {Client} from '@modelcontextprotocol/sdk/client/index.js';\nimport {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';\nimport {executablePath} from 'puppeteer';\n\ndescribe('e2e', () => {\n async function withClient(cb: (client: Client) => Promise\u003cvoid>) {\n const transport = new StdioClientTransport({\n command: 'node',\n args: [\n 'build/src/index.js',\n '--headless',\n '--isolated',\n '--executable-path',\n executablePath(),\n ],\n });\n const client = new Client(\n {\n name: 'e2e-test',\n version: '1.0.0',\n },\n {\n capabilities: {},\n },\n );\n\n try {\n await client.connect(transport);\n await cb(client);\n } finally {\n await client.close();\n }\n }\n it('calls a tool', async () => {\n await withClient(async client => {\n const result = await client.callTool({\n name: 'list_pages',\n arguments: {},\n });\n assert.deepStrictEqual(result, {\n content: [\n {\n type: 'text',\n text: '# list_pages response\\n## Pages\\n0: about:blank [selected]',\n },\n ],\n });\n });\n });\n\n it('calls a tool multiple times', async () => {\n await withClient(async client => {\n let result = await client.callTool({\n name: 'list_pages',\n arguments: {},\n });\n result = await client.callTool({\n name: 'list_pages',\n arguments: {},\n });\n assert.deepStrictEqual(result, {\n content: [\n {\n type: 'text',\n text: '# list_pages response\\n## Pages\\n0: about:blank [selected]',\n },\n ],\n });\n });\n });\n\n it('has all tools', async () => {\n await withClient(async client => {\n const {tools} = await client.listTools();\n const exposedNames = tools.map(t => t.name).sort();\n const files = fs.readdirSync('build/src/tools');\n const definedNames = [];\n for (const file of files) {\n if (file === 'ToolDefinition.js') {\n continue;\n }\n const fileTools = await import(`../src/tools/${file}`);\n for (const maybeTool of Object.values\u003cobject>(fileTools)) {\n if ('name' in maybeTool) {\n definedNames.push(maybeTool.name);\n }\n }\n }\n definedNames.sort();\n assert.deepStrictEqual(exposedNames, definedNames);\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":2629,"content_sha256":"6d5d2b8ea8740e1e1cb2069dee089a80732fdb4c04eca5b77773e941390647ff"},{"filename":"config/servers/tests/McpContext.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport sinon from 'sinon';\n\nimport type {TraceResult} from '../src/trace-processing/parse.js';\n\nimport {html, withMcpContext} from './utils.js';\n\ndescribe('McpContext', () => {\n it('list pages', async () => {\n await withMcpContext(async (_response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton>Click me\u003c/button\n >\u003cinput\n type=\"text\"\n value=\"Input\"\n />`,\n );\n await context.createTextSnapshot();\n assert.ok(await context.getElementByUid('1_1'));\n await context.createTextSnapshot();\n try {\n await context.getElementByUid('1_1');\n assert.fail('not reached');\n } catch (err) {\n assert.strict(\n err.message,\n 'This uid is coming from a stale snapshot. Call take_snapshot to get a fresh snapshot',\n );\n }\n });\n });\n\n it('can store and retrieve performance traces', async () => {\n await withMcpContext(async (_response, context) => {\n const fakeTrace1 = {} as unknown as TraceResult;\n const fakeTrace2 = {} as unknown as TraceResult;\n context.storeTraceRecording(fakeTrace1);\n context.storeTraceRecording(fakeTrace2);\n assert.deepEqual(context.recordedTraces(), [fakeTrace1, fakeTrace2]);\n });\n });\n\n it('should update default timeout when cpu throttling changes', async () => {\n await withMcpContext(async (_response, context) => {\n const page = await context.newPage();\n const timeoutBefore = page.getDefaultTimeout();\n context.setCpuThrottlingRate(2);\n const timeoutAfter = page.getDefaultTimeout();\n assert(timeoutBefore \u003c timeoutAfter, 'Timeout was less then expected');\n });\n });\n\n it('should update default timeout when network conditions changes', async () => {\n await withMcpContext(async (_response, context) => {\n const page = await context.newPage();\n const timeoutBefore = page.getDefaultNavigationTimeout();\n context.setNetworkConditions('Slow 3G');\n const timeoutAfter = page.getDefaultNavigationTimeout();\n assert(timeoutBefore \u003c timeoutAfter, 'Timeout was less then expected');\n });\n });\n\n it('should call waitForEventsAfterAction with correct multipliers', async () => {\n await withMcpContext(async (_response, context) => {\n const page = await context.newPage();\n\n context.setCpuThrottlingRate(2);\n context.setNetworkConditions('Slow 3G');\n const stub = sinon.spy(context, 'getWaitForHelper');\n\n await context.waitForEventsAfterAction(async () => {\n // trigger the waiting only\n });\n\n sinon.assert.calledWithExactly(stub, page, 2, 10);\n });\n });\n\n it('should should detect open DevTools pages', async () => {\n await withMcpContext(\n async (_response, context) => {\n const page = await context.newPage();\n // TODO: we do not know when the CLI flag to auto open DevTools will run\n // so we need this until\n // https://github.com/puppeteer/puppeteer/issues/14368 is there.\n await new Promise(resolve => setTimeout(resolve, 5000));\n await context.createPagesSnapshot();\n assert.ok(context.getDevToolsPage(page));\n },\n {\n autoOpenDevTools: true,\n },\n );\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":3462,"content_sha256":"ddc9a14106d273b785eb68a569de9793c2a5789193512517739891b82b894424"},{"filename":"config/servers/tests/McpResponse.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {readFile, rm} from 'node:fs/promises';\nimport {tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {describe, it} from 'node:test';\n\nimport {\n getImageContent,\n getMockAggregatedIssue,\n getMockRequest,\n getMockResponse,\n getTextContent,\n html,\n stabilizeResponseOutput,\n withMcpContext,\n} from './utils.js';\n\ndescribe('McpResponse', () => {\n it('list pages', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludePages(true);\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('allows response text lines to be added', async t => {\n await withMcpContext(async (response, context) => {\n response.appendResponseLine('Testing 1');\n response.appendResponseLine('Testing 2');\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('does not include anything in response if snapshot is null', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n page.accessibility.snapshot = async () => null;\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n assert.deepStrictEqual(getTextContent(result[0]), `# test response`);\n });\n });\n\n it('returns correctly formatted snapshot for a simple tree', async t => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton>Click me\u003c/button\n >\u003cinput\n type=\"text\"\n value=\"Input\"\n />`,\n );\n await page.focus('button');\n response.includeSnapshot();\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('returns values for textboxes', async t => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003clabel\n >username\u003cinput\n name=\"username\"\n value=\"mcp\"\n />\u003c/label>`,\n );\n await page.focus('input');\n response.includeSnapshot();\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('returns verbose snapshot', async t => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(html`\u003caside>test\u003c/aside>`);\n response.includeSnapshot({\n verbose: true,\n });\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('saves snapshot to file', async t => {\n const filePath = join(tmpdir(), 'test-screenshot.png');\n try {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(html`\u003caside>test\u003c/aside>`);\n response.includeSnapshot({\n verbose: true,\n filePath,\n });\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(stabilizeResponseOutput(getTextContent(result[0])));\n });\n const content = await readFile(filePath, 'utf-8');\n t.assert.snapshot?.(stabilizeResponseOutput(content));\n } finally {\n await rm(filePath, {force: true});\n }\n });\n\n it('adds throttling setting when it is not null', async t => {\n await withMcpContext(async (response, context) => {\n context.setNetworkConditions('Slow 3G');\n const result = await response.handle('test', context);\n assert.equal(result[0].type, 'text');\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('does not include throttling setting when it is null', async () => {\n await withMcpContext(async (response, context) => {\n const result = await response.handle('test', context);\n context.setNetworkConditions(null);\n assert.equal(result[0].type, 'text');\n assert.strictEqual(getTextContent(result[0]), `# test response`);\n });\n });\n it('adds image when image is attached', async () => {\n await withMcpContext(async (response, context) => {\n response.attachImage({data: 'imageBase64', mimeType: 'image/png'});\n const result = await response.handle('test', context);\n assert.strictEqual(getTextContent(result[0]), `# test response`);\n assert.equal(result[1].type, 'image');\n assert.strictEqual(getImageContent(result[1]).data, 'imageBase64');\n assert.strictEqual(getImageContent(result[1]).mimeType, 'image/png');\n });\n });\n\n it('adds cpu throttling setting when it is over 1', async t => {\n await withMcpContext(async (response, context) => {\n context.setCpuThrottlingRate(4);\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('does not include cpu throttling setting when it is 1', async () => {\n await withMcpContext(async (response, context) => {\n context.setCpuThrottlingRate(1);\n const result = await response.handle('test', context);\n assert.strictEqual(getTextContent(result[0]), `# test response`);\n });\n });\n\n it('adds a prompt dialog', async t => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const dialogPromise = new Promise\u003cvoid>(resolve => {\n page.on('dialog', () => {\n resolve();\n });\n });\n page.evaluate(() => {\n prompt('message', 'default');\n });\n await dialogPromise;\n const result = await response.handle('test', context);\n await context.getDialog()?.dismiss();\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('adds an alert dialog', async t => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const dialogPromise = new Promise\u003cvoid>(resolve => {\n page.on('dialog', () => {\n resolve();\n });\n });\n page.evaluate(() => {\n alert('message');\n });\n await dialogPromise;\n const result = await response.handle('test', context);\n await context.getDialog()?.dismiss();\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('add network requests when setting is true', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true);\n context.getNetworkRequests = () => {\n return [getMockRequest({stableId: 1}), getMockRequest({stableId: 2})];\n };\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('does not include network requests when setting is false', async () => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(false);\n context.getNetworkRequests = () => {\n return [getMockRequest()];\n };\n const result = await response.handle('test', context);\n assert.strictEqual(getTextContent(result[0]), `# test response`);\n });\n });\n\n it('add network request when attached with POST data', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true);\n const httpResponse = getMockResponse();\n httpResponse.buffer = () => {\n return Promise.resolve(Buffer.from(JSON.stringify({response: 'body'})));\n };\n httpResponse.headers = () => {\n return {\n 'Content-Type': 'application/json',\n };\n };\n const request = getMockRequest({\n method: 'POST',\n hasPostData: true,\n postData: JSON.stringify({request: 'body'}),\n response: httpResponse,\n });\n context.getNetworkRequests = () => {\n return [request];\n };\n context.getNetworkRequestById = () => {\n return request;\n };\n response.attachNetworkRequest(1);\n\n const result = await response.handle('test', context);\n\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('add network request when attached', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true);\n const request = getMockRequest();\n context.getNetworkRequests = () => {\n return [request];\n };\n context.getNetworkRequestById = () => {\n return request;\n };\n response.attachNetworkRequest(1);\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('adds console messages when the setting is true', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeConsoleData(true);\n const page = context.getSelectedPage();\n const consoleMessagePromise = new Promise\u003cvoid>(resolve => {\n page.on('console', () => {\n resolve();\n });\n });\n page.evaluate(() => {\n console.log('Hello from the test');\n });\n await consoleMessagePromise;\n const result = await response.handle('test', context);\n assert.ok(getTextContent(result[0]));\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('adds a message when no console messages exist', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeConsoleData(true);\n const result = await response.handle('test', context);\n assert.ok(getTextContent(result[0]));\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it(\"doesn't list the issue message if mapping returns null\", async () => {\n await withMcpContext(async (response, context) => {\n const mockAggregatedIssue = getMockAggregatedIssue();\n const mockDescription = {\n file: 'not-existing-description-file.md',\n links: [],\n };\n mockAggregatedIssue.getDescription.returns(mockDescription);\n response.setIncludeConsoleData(true);\n context.getConsoleData = () => {\n return [mockAggregatedIssue];\n };\n\n const result = await response.handle('test', context);\n const text = getTextContent(result[0]);\n assert.ok(text.includes('\u003cno console messages found>'));\n });\n });\n\n it('throws error if mapping returns null on get issue details', async () => {\n await withMcpContext(async (response, context) => {\n const mockAggregatedIssue = getMockAggregatedIssue();\n const mockDescription = {\n file: 'not-existing-description-file.md',\n links: [],\n };\n mockAggregatedIssue.getDescription.returns(mockDescription);\n response.attachConsoleMessage(1);\n context.getConsoleMessageById = () => {\n return mockAggregatedIssue;\n };\n\n try {\n await response.handle('test', context);\n } catch (e) {\n assert.ok(e.message.includes(\"Can't provide detals for the msgid 1\"));\n }\n });\n });\n});\n\ndescribe('McpResponse network request filtering', () => {\n it('filters network requests by resource type', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true, {\n resourceTypes: ['script', 'stylesheet'],\n });\n context.getNetworkRequests = () => {\n return [\n getMockRequest({resourceType: 'script'}),\n getMockRequest({resourceType: 'image'}),\n getMockRequest({resourceType: 'stylesheet'}),\n getMockRequest({resourceType: 'document'}),\n ];\n };\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('filters network requests by single resource type', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true, {\n resourceTypes: ['image'],\n });\n context.getNetworkRequests = () => {\n return [\n getMockRequest({resourceType: 'script'}),\n getMockRequest({resourceType: 'image'}),\n getMockRequest({resourceType: 'stylesheet'}),\n ];\n };\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('shows no requests when filter matches nothing', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true, {\n resourceTypes: ['font'],\n });\n context.getNetworkRequests = () => {\n return [\n getMockRequest({resourceType: 'script'}),\n getMockRequest({resourceType: 'image'}),\n getMockRequest({resourceType: 'stylesheet'}),\n ];\n };\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('shows all requests when no filters are provided', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true);\n context.getNetworkRequests = () => {\n return [\n getMockRequest({resourceType: 'script'}),\n getMockRequest({resourceType: 'image'}),\n getMockRequest({resourceType: 'stylesheet'}),\n getMockRequest({resourceType: 'document'}),\n getMockRequest({resourceType: 'font'}),\n ];\n };\n const result = await response.handle('test', context);\n\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n\n it('shows all requests when empty resourceTypes array is provided', async t => {\n await withMcpContext(async (response, context) => {\n response.setIncludeNetworkRequests(true, {\n resourceTypes: [],\n });\n context.getNetworkRequests = () => {\n return [\n getMockRequest({resourceType: 'script'}),\n getMockRequest({resourceType: 'image'}),\n getMockRequest({resourceType: 'stylesheet'}),\n getMockRequest({resourceType: 'document'}),\n getMockRequest({resourceType: 'font'}),\n ];\n };\n const result = await response.handle('test', context);\n t.assert.snapshot?.(getTextContent(result[0]));\n });\n });\n});\n\ndescribe('McpResponse network pagination', () => {\n it('returns all requests when pagination is not provided', async () => {\n await withMcpContext(async (response, context) => {\n const requests = Array.from({length: 5}, () => getMockRequest());\n context.getNetworkRequests = () => requests;\n response.setIncludeNetworkRequests(true);\n const result = await response.handle('test', context);\n const text = getTextContent(result[0]);\n assert.ok(text.includes('Showing 1-5 of 5 (Page 1 of 1).'));\n assert.ok(!text.includes('Next page:'));\n assert.ok(!text.includes('Previous page:'));\n });\n });\n\n it('returns first page by default', async () => {\n await withMcpContext(async (response, context) => {\n const requests = Array.from({length: 30}, (_, idx) =>\n getMockRequest({method: `GET-${idx}`}),\n );\n context.getNetworkRequests = () => {\n return requests;\n };\n response.setIncludeNetworkRequests(true, {pageSize: 10});\n const result = await response.handle('test', context);\n const text = getTextContent(result[0]);\n assert.ok(text.includes('Showing 1-10 of 30 (Page 1 of 3).'));\n assert.ok(text.includes('Next page: 1'));\n assert.ok(!text.includes('Previous page:'));\n });\n });\n\n it('returns subsequent page when pageIdx provided', async () => {\n await withMcpContext(async (response, context) => {\n const requests = Array.from({length: 25}, (_, idx) =>\n getMockRequest({method: `GET-${idx}`}),\n );\n context.getNetworkRequests = () => requests;\n response.setIncludeNetworkRequests(true, {\n pageSize: 10,\n pageIdx: 1,\n });\n const result = await response.handle('test', context);\n const text = getTextContent(result[0]);\n assert.ok(text.includes('Showing 11-20 of 25 (Page 2 of 3).'));\n assert.ok(text.includes('Next page: 2'));\n assert.ok(text.includes('Previous page: 0'));\n });\n });\n\n it('handles invalid page number by showing first page', async () => {\n await withMcpContext(async (response, context) => {\n const requests = Array.from({length: 5}, () => getMockRequest());\n context.getNetworkRequests = () => requests;\n response.setIncludeNetworkRequests(true, {\n pageSize: 2,\n pageIdx: 10, // Invalid page number\n });\n const result = await response.handle('test', context);\n const text = getTextContent(result[0]);\n assert.ok(\n text.includes('Invalid page number provided. Showing first page.'),\n );\n assert.ok(text.includes('Showing 1-2 of 5 (Page 1 of 3).'));\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":17485,"content_sha256":"656c776779b7383743447fd73bcf99f8f1cdcc0dbfac39fa1a41900129def821"},{"filename":"config/servers/tests/PageCollector.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {beforeEach, describe, it} from 'node:test';\n\nimport type {Frame, HTTPRequest, Target, Protocol} from 'puppeteer-core';\nimport sinon from 'sinon';\n\nimport type {ListenerMap} from '../src/PageCollector.js';\nimport {\n ConsoleCollector,\n NetworkCollector,\n PageCollector,\n} from '../src/PageCollector.js';\nimport {DevTools} from '../src/third_party/index.js';\n\nimport {getMockRequest, getMockBrowser} from './utils.js';\n\ndescribe('PageCollector', () => {\n it('works', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n page.emit('request', request);\n\n assert.equal(collector.getData(page)[0], request);\n });\n\n it('clean up after navigation', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const mainFrame = page.mainFrame();\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n page.emit('request', request);\n\n assert.equal(collector.getData(page)[0], request);\n page.emit('framenavigated', mainFrame);\n\n assert.equal(collector.getData(page).length, 0);\n });\n\n it('does not clean up after sub frame navigation', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n page.emit('request', request);\n page.emit('framenavigated', {} as Frame);\n\n assert.equal(collector.getData(page).length, 1);\n });\n\n it('clean up after navigation and be able to add data after', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const mainFrame = page.mainFrame();\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n page.emit('request', request);\n\n assert.equal(collector.getData(page)[0], request);\n page.emit('framenavigated', mainFrame);\n\n assert.equal(collector.getData(page).length, 0);\n\n page.emit('request', request);\n\n assert.equal(collector.getData(page).length, 1);\n });\n\n it('should only subscribe once', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n browser.emit('targetcreated', {\n page() {\n return Promise.resolve(page);\n },\n } as Target);\n\n // The page inside part is async so we need to await some time\n await new Promise\u003cvoid>(res => res());\n\n assert.equal(collector.getData(page).length, 0);\n\n page.emit('request', request);\n\n assert.equal(collector.getData(page).length, 1);\n\n page.emit('request', request);\n\n assert.equal(collector.getData(page).length, 2);\n });\n\n it('should clear data on page destroy', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const request = getMockRequest();\n const collector = new PageCollector(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n\n page.emit('request', request);\n\n assert.equal(collector.getData(page).length, 1);\n\n browser.emit('targetdestroyed', {\n page() {\n return Promise.resolve(page);\n },\n } as Target);\n\n // The page inside part is async so we need to await some time\n await new Promise\u003cvoid>(res => res());\n\n assert.equal(collector.getData(page).length, 0);\n });\n\n it('should assign ids to requests', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const request1 = getMockRequest();\n const request2 = getMockRequest();\n const collector = new PageCollector\u003cHTTPRequest>(browser, collect => {\n return {\n request: req => {\n collect(req);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n\n page.emit('request', request1);\n page.emit('request', request2);\n\n assert.equal(collector.getData(page).length, 2);\n\n assert.equal(collector.getIdForResource(request1), 1);\n assert.equal(collector.getIdForResource(request2), 2);\n });\n});\n\ndescribe('NetworkCollector', () => {\n it('correctly picks up navigation requests to latest navigation', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const mainFrame = page.mainFrame();\n const request = getMockRequest();\n const navRequest = getMockRequest({\n navigationRequest: true,\n frame: page.mainFrame(),\n });\n const request2 = getMockRequest();\n const collector = new NetworkCollector(browser);\n await collector.init([page]);\n page.emit('request', request);\n page.emit('request', navRequest);\n\n assert.equal(collector.getData(page)[0], request);\n assert.equal(collector.getData(page)[1], navRequest);\n page.emit('framenavigated', mainFrame);\n\n assert.equal(collector.getData(page).length, 1);\n assert.equal(collector.getData(page)[0], navRequest);\n\n page.emit('request', request2);\n\n assert.equal(collector.getData(page).length, 2);\n assert.equal(collector.getData(page)[0], navRequest);\n assert.equal(collector.getData(page)[1], request2);\n });\n\n it('correctly picks up after multiple back to back navigations', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const mainFrame = page.mainFrame();\n const navRequest = getMockRequest({\n navigationRequest: true,\n frame: page.mainFrame(),\n });\n const navRequest2 = getMockRequest({\n navigationRequest: true,\n frame: page.mainFrame(),\n });\n const request = getMockRequest();\n\n const collector = new NetworkCollector(browser);\n await collector.init([page]);\n page.emit('request', navRequest);\n assert.equal(collector.getData(page)[0], navRequest);\n\n page.emit('framenavigated', mainFrame);\n assert.equal(collector.getData(page).length, 1);\n assert.equal(collector.getData(page)[0], navRequest);\n\n page.emit('request', navRequest2);\n assert.equal(collector.getData(page).length, 2);\n assert.equal(collector.getData(page)[0], navRequest);\n assert.equal(collector.getData(page)[1], navRequest2);\n\n page.emit('framenavigated', mainFrame);\n assert.equal(collector.getData(page).length, 1);\n assert.equal(collector.getData(page)[0], navRequest2);\n\n page.emit('request', request);\n assert.equal(collector.getData(page).length, 2);\n });\n\n it('works with previous navigations', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n const mainFrame = page.mainFrame();\n const navRequest = getMockRequest({\n navigationRequest: true,\n frame: page.mainFrame(),\n });\n const navRequest2 = getMockRequest({\n navigationRequest: true,\n frame: page.mainFrame(),\n });\n const request = getMockRequest();\n\n const collector = new NetworkCollector(browser);\n await collector.init([page]);\n page.emit('request', navRequest);\n assert.equal(collector.getData(page, true).length, 1);\n\n page.emit('framenavigated', mainFrame);\n assert.equal(collector.getData(page, true).length, 1);\n\n page.emit('request', navRequest2);\n assert.equal(collector.getData(page, true).length, 2);\n\n page.emit('framenavigated', mainFrame);\n assert.equal(collector.getData(page, true).length, 2);\n\n page.emit('request', request);\n assert.equal(collector.getData(page, true).length, 3);\n });\n});\n\ndescribe('ConsoleCollector', () => {\n let issue: Protocol.Audits.InspectorIssue;\n\n beforeEach(() => {\n issue = {\n code: 'MixedContentIssue',\n details: {\n mixedContentIssueDetails: {\n insecureURL: 'test.url',\n resolutionStatus: 'MixedContentBlocked',\n mainResourceURL: '',\n },\n },\n };\n });\n\n it('emits issues on page', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n // @ts-expect-error internal API.\n const cdpSession = page._client();\n const onIssuesListener = sinon.spy();\n\n page.on('issue', onIssuesListener);\n\n const collector = new ConsoleCollector(browser, collect => {\n return {\n issue: issue => {\n collect(issue as DevTools.AggregatedIssue);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n cdpSession.emit('Audits.issueAdded', {issue});\n sinon.assert.calledOnce(onIssuesListener);\n\n const issueArgument = onIssuesListener.getCall(0).args[0];\n assert(issueArgument instanceof DevTools.AggregatedIssue);\n });\n\n it('collects issues', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n // @ts-expect-error internal API.\n const cdpSession = page._client();\n\n const collector = new ConsoleCollector(browser, collect => {\n return {\n issue: issue => {\n collect(issue as DevTools.AggregatedIssue);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n\n const issue2 = {\n code: 'ElementAccessibilityIssue' as const,\n details: {\n elementAccessibilityIssueDetails: {\n nodeId: 1,\n elementAccessibilityIssueReason: 'DisallowedSelectChild',\n hasDisallowedAttributes: true,\n },\n },\n } satisfies Protocol.Audits.InspectorIssue;\n\n cdpSession.emit('Audits.issueAdded', {issue});\n cdpSession.emit('Audits.issueAdded', {issue: issue2});\n const data = collector.getData(page);\n assert.equal(data.length, 2);\n });\n\n it('filters duplicated issues', async () => {\n const browser = getMockBrowser();\n const page = (await browser.pages())[0];\n // @ts-expect-error internal API.\n const cdpSession = page._client();\n\n const collector = new ConsoleCollector(browser, collect => {\n return {\n issue: issue => {\n collect(issue as DevTools.AggregatedIssue);\n },\n } as ListenerMap;\n });\n await collector.init([page]);\n\n cdpSession.emit('Audits.issueAdded', {issue});\n cdpSession.emit('Audits.issueAdded', {issue});\n const data = collector.getData(page);\n assert.equal(data.length, 1);\n const collectedIssue = data[0];\n assert(collectedIssue instanceof DevTools.AggregatedIssue);\n assert.equal(collectedIssue.code(), 'MixedContentIssue');\n assert.equal(collectedIssue.getAggregatedIssuesCount(), 1);\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":11503,"content_sha256":"0585b33d72b4d1a65328f819c4bac94d133e56dcf354747393c99dfe63dabdb6"},{"filename":"config/servers/tests/server.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport http, {\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from 'node:http';\nimport {before, after, afterEach} from 'node:test';\n\nimport {html} from './utils.js';\n\nclass TestServer {\n #port: number;\n #server: Server;\n\n static randomPort() {\n /**\n * Some ports are restricted by Chromium and will fail to connect\n * to prevent we start after the\n *\n * https://source.chromium.org/chromium/chromium/src/+/main:net/base/port_util.cc;l=107?q=kRestrictedPorts&ss=chromium\n */\n const min = 10101;\n const max = 20202;\n return Math.floor(Math.random() * (max - min + 1) + min);\n }\n\n #routes: Record\u003cstring, (req: IncomingMessage, res: ServerResponse) => void> =\n {};\n\n constructor(port: number) {\n this.#port = port;\n this.#server = http.createServer((req, res) => this.#handle(req, res));\n }\n\n get baseUrl(): string {\n return `http://localhost:${this.#port}`;\n }\n\n getRoute(path: string) {\n if (!this.#routes[path]) {\n throw new Error(`Route ${path} was not setup.`);\n }\n return `${this.baseUrl}${path}`;\n }\n\n addHtmlRoute(path: string, htmlContent: string) {\n if (this.#routes[path]) {\n throw new Error(`Route ${path} was already setup.`);\n }\n this.#routes[path] = (_req: IncomingMessage, res: ServerResponse) => {\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.statusCode = 200;\n res.end(htmlContent);\n };\n }\n\n addRoute(\n path: string,\n handler: (req: IncomingMessage, res: ServerResponse) => void,\n ) {\n if (this.#routes[path]) {\n throw new Error(`Route ${path} was already setup.`);\n }\n this.#routes[path] = handler;\n }\n\n #handle(req: IncomingMessage, res: ServerResponse) {\n const url = req.url ?? '';\n const routeHandler = this.#routes[url];\n\n if (routeHandler) {\n routeHandler(req, res);\n } else {\n res.writeHead(404, {'Content-Type': 'text/html'});\n res.end(\n html`\u003ch1>404 - Not Found\u003c/h1>\u003cp>The requested page does not exist.\u003c/p>`,\n );\n }\n }\n\n restore() {\n this.#routes = {};\n }\n\n start(): Promise\u003cvoid> {\n return new Promise(res => {\n this.#server.listen(this.#port, res);\n });\n }\n\n stop(): Promise\u003cvoid> {\n return new Promise((res, rej) => {\n this.#server.close(err => {\n if (err) {\n rej(err);\n } else {\n res();\n }\n });\n });\n }\n}\n\nexport function serverHooks() {\n const server = new TestServer(TestServer.randomPort());\n before(async () => {\n await server.start();\n });\n after(async () => {\n await server.stop();\n });\n afterEach(() => {\n server.restore();\n });\n\n return server;\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":2773,"content_sha256":"6afc75f293c46355ad1c67c67387b1dd12be1c2f4bdab6937bb5b2958216d642"},{"filename":"config/servers/tests/setup.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport '../src/polyfill.js';\n\nimport path from 'node:path';\nimport {it} from 'node:test';\n\nif (!it.snapshot) {\n it.snapshot = {\n setResolveSnapshotPath: () => {\n // Internally empty\n },\n setDefaultSnapshotSerializers: () => {\n // Internally empty\n },\n };\n}\n\n// This is run by Node when we execute the tests via the --import flag.\nit.snapshot.setResolveSnapshotPath(testPath => {\n // By default the snapshots go into the build directory, but we want them\n // in the tests/ directory.\n const correctPath = testPath?.replace(path.join('build', 'tests'), 'tests');\n return correctPath + '.snapshot';\n});\n\n// The default serializer is JSON.stringify which outputs a very hard to read\n// snapshot. So we override it to one that shows new lines literally rather\n// than via `\\n`.\nit.snapshot.setDefaultSnapshotSerializers([String]);\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":943,"content_sha256":"4d65a95e742c44454866ecfdc756323408b0fdaf873a5d3156e0cb0d9ada8ba0"},{"filename":"config/servers/tests/snapshot.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\ninterface ScreenshotData {\n html: string;\n}\n\nexport const screenshots: Record\u003cstring, ScreenshotData> = {\n basic: {\n html: '\u003cdiv>Hello MCP\u003c/div>',\n },\n viewportOverflow: {\n html: '\u003cdiv style=\"height: 120vh; background-color: rebeccapurple;\">View Port overflow\u003c/div>',\n },\n button: {\n html: '\u003cbutton>I am button click me\u003c/button>',\n },\n};\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":443,"content_sha256":"eeea30df68a87cdac30df131f42c7899848186a04bc9f8f4b18f2b83b0aaa0b0"},{"filename":"config/servers/tests/tools/console.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {before, describe, it} from 'node:test';\n\nimport {loadIssueDescriptions} from '../../src/issue-descriptions.js';\nimport {McpResponse} from '../../src/McpResponse.js';\nimport {DevTools} from '../../src/third_party/index.js';\nimport {\n getConsoleMessage,\n listConsoleMessages,\n} from '../../src/tools/console.js';\nimport {serverHooks} from '../server.js';\nimport {getTextContent, withMcpContext} from '../utils.js';\n\ndescribe('console', () => {\n before(async () => {\n await loadIssueDescriptions();\n });\n describe('list_console_messages', () => {\n it('list messages', async () => {\n await withMcpContext(async (response, context) => {\n await listConsoleMessages.handler({params: {}}, response, context);\n assert.ok(response.includeConsoleData);\n });\n });\n\n it('lists error messages', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n await page.setContent(\n '\u003cscript>console.error(\"This is an error\")\u003c/script>',\n );\n await listConsoleMessages.handler({params: {}}, response, context);\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(textContent.includes('msgid=1 [error] This is an error'));\n });\n });\n\n it('work with primitive unhandled errors', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n await page.setContent('\u003cscript>throw undefined;\u003c/script>');\n await listConsoleMessages.handler({params: {}}, response, context);\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(textContent.includes('msgid=1 [error] undefined (0 args)'));\n });\n });\n\n describe('issues', () => {\n it('lists issues', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n const issuePromise = new Promise\u003cvoid>(resolve => {\n page.once('issue', () => {\n resolve();\n });\n });\n await page.setContent('\u003cinput type=\"text\" name=\"username\" />');\n await issuePromise;\n await listConsoleMessages.handler({params: {}}, response, context);\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(\n textContent.includes(\n `msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`,\n ),\n );\n });\n });\n\n it('lists issues after a page reload', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n const issuePromise = new Promise\u003cvoid>(resolve => {\n page.once('issue', () => {\n resolve();\n });\n });\n\n await page.setContent('\u003cinput type=\"text\" name=\"username\" />');\n await issuePromise;\n await listConsoleMessages.handler({params: {}}, response, context);\n {\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(\n textContent.includes(\n `msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`,\n ),\n );\n }\n\n const anotherIssuePromise = new Promise\u003cvoid>(resolve => {\n page.once('issue', () => {\n resolve();\n });\n });\n await page.reload();\n await page.setContent('\u003cinput type=\"text\" name=\"username\" />');\n await anotherIssuePromise;\n {\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(\n textContent.includes(\n `msgid=2 [issue] An element doesn't have an autocomplete attribute (count: 1)`,\n ),\n );\n }\n });\n });\n });\n });\n\n describe('get_console_message', () => {\n it('gets a specific console message', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n await page.setContent(\n '\u003cscript>console.error(\"This is an error\")\u003c/script>',\n );\n // The list is needed to populate the console messages in the context.\n await listConsoleMessages.handler({params: {}}, response, context);\n await getConsoleMessage.handler(\n {params: {msgid: 1}},\n response,\n context,\n );\n const formattedResponse = await response.handle('test', context);\n const textContent = getTextContent(formattedResponse[0]);\n assert.ok(\n textContent.includes('msgid=1 [error] This is an error'),\n 'Should contain console message body',\n );\n });\n });\n\n describe('issues type', () => {\n const server = serverHooks();\n\n it('gets issue details with node id parsing', async t => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n const issuePromise = new Promise\u003cvoid>(resolve => {\n page.once('issue', () => {\n resolve();\n });\n });\n await page.setContent('\u003cinput type=\"text\" name=\"username\" />');\n await context.createTextSnapshot();\n await issuePromise;\n await listConsoleMessages.handler({params: {}}, response, context);\n const response2 = new McpResponse();\n await getConsoleMessage.handler(\n {params: {msgid: 1}},\n response2,\n context,\n );\n const formattedResponse = await response2.handle('test', context);\n t.assert.snapshot?.(getTextContent(formattedResponse[0]));\n });\n });\n it('gets issue details with request id parsing', async t => {\n server.addRoute('/data.json', (_req, res) => {\n res.setHeader('Content-Type', 'application/json');\n res.statusCode = 200;\n res.end(JSON.stringify({data: 'test data'}));\n });\n\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n const issuePromise = new Promise\u003cvoid>(resolve => {\n page.once('issue', () => {\n resolve();\n });\n });\n\n const url = server.getRoute('/data.json');\n await page.setContent(`\n \u003cscript>\n fetch('${url}', {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Custom-Header': 'MyValue'\n }\n });\n \u003c/script>\n `);\n await context.createTextSnapshot();\n await issuePromise;\n const messages = context.getConsoleData();\n let issueMsg;\n for (const message of messages) {\n if (message instanceof DevTools.AggregatedIssue) {\n issueMsg = message;\n break;\n }\n }\n assert.ok(issueMsg);\n const id = context.getConsoleMessageStableId(issueMsg);\n assert.ok(id);\n await listConsoleMessages.handler(\n {params: {types: ['issue']}},\n response,\n context,\n );\n const response2 = new McpResponse();\n await getConsoleMessage.handler(\n {params: {msgid: id}},\n response2,\n context,\n );\n const formattedResponse = await response2.handle('test', context);\n const rawText = getTextContent(formattedResponse[0]);\n const sanitizedText = rawText\n .replaceAll(/ID: \\d+/g, 'ID: \u003cID>')\n .replaceAll(/reqid=\\d+/g, 'reqid=\u003creqid>')\n .replaceAll(/localhost:\\d+/g, 'hostname:port');\n t.assert.snapshot?.(sanitizedText);\n });\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":8487,"content_sha256":"277be4293dd5c8d1be044d67d3bdea0c5470022ac0f4c7607c9305798a8e10fb"},{"filename":"config/servers/tests/tools/emulation.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {emulate} from '../../src/tools/emulation.js';\nimport {withMcpContext} from '../utils.js';\n\ndescribe('emulation', () => {\n describe('network', () => {\n it('emulates offline network conditions', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n networkConditions: 'Offline',\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getNetworkConditions(), 'Offline');\n });\n });\n it('emulates network throttling when the throttling option is valid', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n networkConditions: 'Slow 3G',\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getNetworkConditions(), 'Slow 3G');\n });\n });\n\n it('disables network emulation', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n networkConditions: 'No emulation',\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getNetworkConditions(), null);\n });\n });\n\n it('does not set throttling when the network throttling is not one of the predefined options', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n networkConditions: 'Slow 11G',\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getNetworkConditions(), null);\n });\n });\n\n it('report correctly for the currently selected page', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n networkConditions: 'Slow 3G',\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getNetworkConditions(), 'Slow 3G');\n\n const page = await context.newPage();\n context.selectPage(page);\n\n assert.strictEqual(context.getNetworkConditions(), null);\n });\n });\n });\n\n describe('cpu', () => {\n it('emulates cpu throttling when the rate is valid (1-20x)', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n cpuThrottlingRate: 4,\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getCpuThrottlingRate(), 4);\n });\n });\n\n it('disables cpu throttling', async () => {\n await withMcpContext(async (response, context) => {\n context.setCpuThrottlingRate(4); // Set it to something first.\n await emulate.handler(\n {\n params: {\n cpuThrottlingRate: 1,\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getCpuThrottlingRate(), 1);\n });\n });\n\n it('report correctly for the currently selected page', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n cpuThrottlingRate: 4,\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getCpuThrottlingRate(), 4);\n\n const page = await context.newPage();\n context.selectPage(page);\n\n assert.strictEqual(context.getCpuThrottlingRate(), 1);\n });\n });\n });\n\n describe('geolocation', () => {\n it('emulates geolocation with latitude and longitude', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n geolocation: {\n latitude: 48.137154,\n longitude: 11.576124,\n },\n },\n },\n response,\n context,\n );\n\n const geolocation = context.getGeolocation();\n assert.strictEqual(geolocation?.latitude, 48.137154);\n assert.strictEqual(geolocation?.longitude, 11.576124);\n });\n });\n\n it('clears geolocation override when geolocation is set to null', async () => {\n await withMcpContext(async (response, context) => {\n // First set a geolocation\n await emulate.handler(\n {\n params: {\n geolocation: {\n latitude: 48.137154,\n longitude: 11.576124,\n },\n },\n },\n response,\n context,\n );\n\n assert.notStrictEqual(context.getGeolocation(), null);\n\n // Then clear it by setting geolocation to null\n await emulate.handler(\n {\n params: {\n geolocation: null,\n },\n },\n response,\n context,\n );\n\n assert.strictEqual(context.getGeolocation(), null);\n });\n });\n\n it('reports correctly for the currently selected page', async () => {\n await withMcpContext(async (response, context) => {\n await emulate.handler(\n {\n params: {\n geolocation: {\n latitude: 48.137154,\n longitude: 11.576124,\n },\n },\n },\n response,\n context,\n );\n\n const geolocation = context.getGeolocation();\n assert.strictEqual(geolocation?.latitude, 48.137154);\n assert.strictEqual(geolocation?.longitude, 11.576124);\n\n const page = await context.newPage();\n context.selectPage(page);\n\n assert.strictEqual(context.getGeolocation(), null);\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":6137,"content_sha256":"0f718980c2bfd22ca81b8096bb689021de1489ed8f4c57230f6cdaccf1fada67"},{"filename":"config/servers/tests/tools/input.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport {describe, it} from 'node:test';\n\nimport {\n click,\n hover,\n fill,\n drag,\n fillForm,\n uploadFile,\n pressKey,\n} from '../../src/tools/input.js';\nimport {parseKey} from '../../src/utils/keyboard.js';\nimport {serverHooks} from '../server.js';\nimport {html, withMcpContext} from '../utils.js';\n\ndescribe('input', () => {\n const server = serverHooks();\n\n describe('click', () => {\n it('clicks', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton onclick=\"this.innerText = 'clicked';\">test\u003c/button>`,\n );\n await context.createTextSnapshot();\n await click.handler(\n {\n params: {\n uid: '1_1',\n },\n },\n response,\n context,\n );\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully clicked on the element',\n );\n assert.ok(response.includeSnapshot);\n assert.ok(await page.$('text/clicked'));\n });\n });\n it('double clicks', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton ondblclick=\"this.innerText = 'dblclicked';\"\n >test\u003c/button\n >`,\n );\n await context.createTextSnapshot();\n await click.handler(\n {\n params: {\n uid: '1_1',\n dblClick: true,\n },\n },\n response,\n context,\n );\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully double clicked on the element',\n );\n assert.ok(response.includeSnapshot);\n assert.ok(await page.$('text/dblclicked'));\n });\n });\n it('waits for navigation', async () => {\n const resolveNavigation = Promise.withResolvers\u003cvoid>();\n server.addHtmlRoute(\n '/link',\n html`\u003ca href=\"/navigated\">Navigate page\u003c/a>`,\n );\n server.addRoute('/navigated', async (_req, res) => {\n await resolveNavigation.promise;\n res.write(html`\u003cmain>I was navigated\u003c/main>`);\n res.end();\n });\n\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/link'));\n await context.createTextSnapshot();\n const clickPromise = click.handler(\n {\n params: {\n uid: '1_1',\n },\n },\n response,\n context,\n );\n const [t1, t2] = await Promise.all([\n clickPromise.then(() => Date.now()),\n new Promise\u003cnumber>(res => {\n setTimeout(() => {\n resolveNavigation.resolve();\n res(Date.now());\n }, 300);\n }),\n ]);\n\n assert(t1 > t2, 'Waited for navigation');\n });\n });\n\n it('waits for stable DOM', async () => {\n server.addHtmlRoute(\n '/unstable',\n html`\n \u003cbutton>Click to change to see time\u003c/button>\n \u003cscript>\n const button = document.querySelector('button');\n button.addEventListener('click', () => {\n setTimeout(() => {\n button.textContent = Date.now();\n }, 50);\n });\n \u003c/script>\n `,\n );\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/unstable'));\n await context.createTextSnapshot();\n const handlerResolveTime = await click\n .handler(\n {\n params: {\n uid: '1_1',\n },\n },\n response,\n context,\n )\n .then(() => Date.now());\n const buttonChangeTime = await page.evaluate(() => {\n const button = document.querySelector('button');\n return Number(button?.textContent);\n });\n\n assert(handlerResolveTime > buttonChangeTime, 'Waited for navigation');\n });\n });\n });\n\n describe('hover', () => {\n it('hovers', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton onmouseover=\"this.innerText = 'hovered';\">test\u003c/button>`,\n );\n await context.createTextSnapshot();\n await hover.handler(\n {\n params: {\n uid: '1_1',\n },\n },\n response,\n context,\n );\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully hovered over the element',\n );\n assert.ok(response.includeSnapshot);\n assert.ok(await page.$('text/hovered'));\n });\n });\n });\n\n describe('fill', () => {\n it('fills out an input', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(html`\u003cinput />`);\n await context.createTextSnapshot();\n await fill.handler(\n {\n params: {\n uid: '1_1',\n value: 'test',\n },\n },\n response,\n context,\n );\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully filled out the element',\n );\n assert.ok(response.includeSnapshot);\n assert.ok(await page.$('text/test'));\n });\n });\n\n it('fills out a select by text', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cselect\n >\u003coption value=\"v1\">one\u003c/option\n >\u003coption value=\"v2\">two\u003c/option>\u003c/select\n >`,\n );\n await context.createTextSnapshot();\n await fill.handler(\n {\n params: {\n uid: '1_1',\n value: 'two',\n },\n },\n response,\n context,\n );\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully filled out the element',\n );\n assert.ok(response.includeSnapshot);\n const selectedValue = await page.evaluate(\n () => document.querySelector('select')!.value,\n );\n assert.strictEqual(selectedValue, 'v2');\n });\n });\n });\n\n describe('drags', () => {\n it('drags one element onto another', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cdiv\n role=\"button\"\n id=\"drag\"\n draggable=\"true\"\n >drag me\u003c/div\n >\n \u003cdiv\n id=\"drop\"\n aria-label=\"drop\"\n style=\"width: 100px; height: 100px; border: 1px solid black;\"\n ondrop=\"this.innerText = 'dropped';\"\n >\n \u003c/div>\n \u003cscript>\n drag.addEventListener('dragstart', event => {\n event.dataTransfer.setData('text/plain', event.target.id);\n });\n drop.addEventListener('dragover', event => {\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n });\n drop.addEventListener('drop', event => {\n event.preventDefault();\n const data = event.dataTransfer.getData('text/plain');\n event.target.appendChild(document.getElementById(data));\n });\n \u003c/script>`,\n );\n await context.createTextSnapshot();\n await drag.handler(\n {\n params: {\n from_uid: '1_1',\n to_uid: '1_2',\n },\n },\n response,\n context,\n );\n assert.ok(response.includeSnapshot);\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully dragged an element',\n );\n assert.ok(await page.$('text/dropped'));\n });\n });\n });\n\n describe('fill form', () => {\n it('successfully fills out the form', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cform>\n \u003clabel\n >username\u003cinput\n name=\"username\"\n type=\"text\"\n />\u003c/label>\n \u003clabel\n >email\u003cinput\n name=\"email\"\n type=\"text\"\n />\u003c/label>\n \u003cinput\n type=\"submit\"\n value=\"Submit\"\n />\n \u003c/form>`,\n );\n await context.createTextSnapshot();\n await fillForm.handler(\n {\n params: {\n elements: [\n {\n uid: '1_2',\n value: 'test',\n },\n {\n uid: '1_4',\n value: 'test2',\n },\n ],\n },\n },\n response,\n context,\n );\n assert.ok(response.includeSnapshot);\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully filled out the form',\n );\n assert.deepStrictEqual(\n await page.evaluate(() => {\n return [\n // @ts-expect-error missing types\n document.querySelector('input[name=username]').value,\n // @ts-expect-error missing types\n document.querySelector('input[name=email]').value,\n ];\n }),\n ['test', 'test2'],\n );\n });\n });\n });\n\n describe('uploadFile', () => {\n it('uploads a file to a file input', async () => {\n const testFilePath = path.join(process.cwd(), 'test.txt');\n await fs.writeFile(testFilePath, 'test file content');\n\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cform>\n \u003cinput\n type=\"file\"\n id=\"file-input\"\n />\n \u003c/form>`,\n );\n await context.createTextSnapshot();\n await uploadFile.handler(\n {\n params: {\n uid: '1_1',\n filePath: testFilePath,\n },\n },\n response,\n context,\n );\n assert.ok(response.includeSnapshot);\n assert.strictEqual(\n response.responseLines[0],\n `File uploaded from ${testFilePath}.`,\n );\n });\n\n await fs.unlink(testFilePath);\n });\n\n it('uploads a file when clicking an element opens a file uploader', async () => {\n const testFilePath = path.join(process.cwd(), 'test.txt');\n await fs.writeFile(testFilePath, 'test file content');\n\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cbutton id=\"file-chooser-button\">Upload file\u003c/button>\n \u003cinput\n type=\"file\"\n id=\"file-input\"\n style=\"display: none;\"\n />\n \u003cscript>\n document\n .getElementById('file-chooser-button')\n .addEventListener('click', () => {\n document.getElementById('file-input').click();\n });\n \u003c/script>`,\n );\n await context.createTextSnapshot();\n await uploadFile.handler(\n {\n params: {\n uid: '1_1',\n filePath: testFilePath,\n },\n },\n response,\n context,\n );\n assert.ok(response.includeSnapshot);\n assert.strictEqual(\n response.responseLines[0],\n `File uploaded from ${testFilePath}.`,\n );\n const uploadedFileName = await page.$eval('#file-input', el => {\n const input = el as HTMLInputElement;\n return input.files?.[0]?.name;\n });\n assert.strictEqual(uploadedFileName, 'test.txt');\n\n await fs.unlink(testFilePath);\n });\n });\n\n it('throws an error if the element is not a file input and does not open a file chooser', async () => {\n const testFilePath = path.join(process.cwd(), 'test.txt');\n await fs.writeFile(testFilePath, 'test file content');\n\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(html`\u003cdiv>Not a file input\u003c/div>`);\n await context.createTextSnapshot();\n\n await assert.rejects(\n uploadFile.handler(\n {\n params: {\n uid: '1_1',\n filePath: testFilePath,\n },\n },\n response,\n context,\n ),\n {\n message:\n 'Failed to upload file. The element could not accept the file directly, and clicking it did not trigger a file chooser.',\n },\n );\n\n assert.strictEqual(response.responseLines.length, 0);\n assert.strictEqual(response.snapshotParams, undefined);\n\n await fs.unlink(testFilePath);\n });\n });\n });\n\n describe('press_key', () => {\n it('parses keys', () => {\n assert.deepStrictEqual(parseKey('Shift+A'), ['A', 'Shift']);\n assert.deepStrictEqual(parseKey('Shift++'), ['+', 'Shift']);\n assert.deepStrictEqual(parseKey('Control+Shift++'), [\n '+',\n 'Control',\n 'Shift',\n ]);\n assert.deepStrictEqual(parseKey('Shift'), ['Shift']);\n assert.deepStrictEqual(parseKey('KeyA'), ['KeyA']);\n });\n it('throws on empty key', () => {\n assert.throws(() => {\n parseKey('');\n });\n });\n it('throws on invalid key', () => {\n assert.throws(() => {\n parseKey('aaaaa');\n });\n });\n it('throws on multiple keys', () => {\n assert.throws(() => {\n parseKey('Shift+Shift');\n });\n });\n\n it('processes press_key', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.setContent(\n html`\u003cscript>\n logs = [];\n document.addEventListener('keydown', e => logs.push('d' + e.key));\n document.addEventListener('keyup', e => logs.push('u' + e.key));\n \u003c/script>`,\n );\n await context.createTextSnapshot();\n\n await pressKey.handler(\n {\n params: {\n key: 'Control+Shift+C',\n },\n },\n response,\n context,\n );\n\n assert.deepStrictEqual(await page.evaluate('logs'), [\n 'dControl',\n 'dShift',\n 'dC',\n 'uC',\n 'uShift',\n 'uControl',\n ]);\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":15380,"content_sha256":"5e2eb7cf4075202ce60d7fae7edd35c0345c36632f3b8108b7493261706d9ae1"},{"filename":"config/servers/tests/tools/network.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {\n getNetworkRequest,\n listNetworkRequests,\n} from '../../src/tools/network.js';\nimport {serverHooks} from '../server.js';\nimport {\n getTextContent,\n html,\n stabilizeResponseOutput,\n withMcpContext,\n} from '../utils.js';\n\ndescribe('network', () => {\n const server = serverHooks();\n describe('network_list_requests', () => {\n it('list requests', async () => {\n await withMcpContext(async (response, context) => {\n await listNetworkRequests.handler({params: {}}, response, context);\n assert.ok(response.includeNetworkRequests);\n assert.strictEqual(response.networkRequestsPageIdx, undefined);\n });\n });\n\n it('list requests form current navigations only', async t => {\n server.addHtmlRoute('/one', html`\u003cmain>First\u003c/main>`);\n server.addHtmlRoute('/two', html`\u003cmain>Second\u003c/main>`);\n server.addHtmlRoute('/three', html`\u003cmain>Third\u003c/main>`);\n\n await withMcpContext(async (response, context) => {\n await context.setUpNetworkCollectorForTesting();\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/one'));\n await page.goto(server.getRoute('/two'));\n await page.goto(server.getRoute('/three'));\n await listNetworkRequests.handler(\n {\n params: {},\n },\n response,\n context,\n );\n const responseData = await response.handle('list_request', context);\n t.assert.snapshot?.(\n stabilizeResponseOutput(getTextContent(responseData[0])),\n );\n });\n });\n\n it('list requests from previous navigations', async t => {\n server.addHtmlRoute('/one', html`\u003cmain>First\u003c/main>`);\n server.addHtmlRoute('/two', html`\u003cmain>Second\u003c/main>`);\n server.addHtmlRoute('/three', html`\u003cmain>Third\u003c/main>`);\n\n await withMcpContext(async (response, context) => {\n await context.setUpNetworkCollectorForTesting();\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/one'));\n await page.goto(server.getRoute('/two'));\n await page.goto(server.getRoute('/three'));\n await listNetworkRequests.handler(\n {\n params: {\n includePreservedRequests: true,\n },\n },\n response,\n context,\n );\n const responseData = await response.handle('list_request', context);\n t.assert.snapshot?.(\n stabilizeResponseOutput(getTextContent(responseData[0])),\n );\n });\n });\n\n it('list requests from previous navigations from redirects', async t => {\n server.addRoute('/redirect', async (_req, res) => {\n res.writeHead(302, {\n Location: server.getRoute('/redirected'),\n });\n res.end();\n });\n\n server.addHtmlRoute(\n '/redirected',\n html`\u003cscript>\n document.location.href = '/redirected-page';\n \u003c/script>`,\n );\n\n server.addHtmlRoute(\n '/redirected-page',\n html`\u003cmain>I was redirected 2 times\u003c/main>`,\n );\n\n await withMcpContext(async (response, context) => {\n await context.setUpNetworkCollectorForTesting();\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/redirect'));\n await listNetworkRequests.handler(\n {\n params: {\n includePreservedRequests: true,\n },\n },\n response,\n context,\n );\n const responseData = await response.handle('list_request', context);\n t.assert.snapshot?.(\n stabilizeResponseOutput(getTextContent(responseData[0])),\n );\n });\n });\n });\n describe('network_get_request', () => {\n it('attaches request', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto('data:text/html,\u003cdiv>Hello MCP\u003c/div>');\n await getNetworkRequest.handler(\n {params: {reqid: 1}},\n response,\n context,\n );\n\n assert.equal(response.attachedNetworkRequestId, 1);\n });\n });\n it('should not add the request list', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto('data:text/html,\u003cdiv>Hello MCP\u003c/div>');\n await getNetworkRequest.handler(\n {params: {reqid: 1}},\n response,\n context,\n );\n assert(!response.includeNetworkRequests);\n });\n });\n it('should get request from previous navigations', async t => {\n server.addHtmlRoute('/one', html`\u003cmain>First\u003c/main>`);\n server.addHtmlRoute('/two', html`\u003cmain>Second\u003c/main>`);\n server.addHtmlRoute('/three', html`\u003cmain>Third\u003c/main>`);\n\n await withMcpContext(async (response, context) => {\n await context.setUpNetworkCollectorForTesting();\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/one'));\n await page.goto(server.getRoute('/two'));\n await page.goto(server.getRoute('/three'));\n await getNetworkRequest.handler(\n {\n params: {\n reqid: 1,\n },\n },\n response,\n context,\n );\n const responseData = await response.handle('get_request', context);\n\n t.assert.snapshot?.(\n stabilizeResponseOutput(getTextContent(responseData[0])),\n );\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":5697,"content_sha256":"2bd922aabc3b7800d72ae9b724e3e7818d618d81fca37c3e3fa26c2f24b87474"},{"filename":"config/servers/tests/tools/pages.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport type {Dialog} from 'puppeteer-core';\n\nimport {\n listPages,\n newPage,\n closePage,\n selectPage,\n navigatePage,\n resizePage,\n handleDialog,\n} from '../../src/tools/pages.js';\nimport {withMcpContext} from '../utils.js';\n\ndescribe('pages', () => {\n describe('list_pages', () => {\n it('list pages', async () => {\n await withMcpContext(async (response, context) => {\n await listPages.handler({params: {}}, response, context);\n assert.ok(response.includePages);\n });\n });\n });\n describe('new_page', () => {\n it('create a page', async () => {\n await withMcpContext(async (response, context) => {\n assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());\n await newPage.handler(\n {params: {url: 'about:blank'}},\n response,\n context,\n );\n assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());\n assert.ok(response.includePages);\n });\n });\n });\n describe('close_page', () => {\n it('closes a page', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());\n assert.strictEqual(context.getPageByIdx(1), page);\n await closePage.handler({params: {pageIdx: 1}}, response, context);\n assert.ok(page.isClosed());\n assert.ok(response.includePages);\n });\n });\n it('cannot close the last page', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await closePage.handler({params: {pageIdx: 0}}, response, context);\n assert.deepStrictEqual(\n response.responseLines[0],\n `The last open page cannot be closed. It is fine to keep it open.`,\n );\n assert.ok(response.includePages);\n assert.ok(!page.isClosed());\n });\n });\n });\n describe('select_page', () => {\n it('selects a page', async () => {\n await withMcpContext(async (response, context) => {\n await context.newPage();\n assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());\n await selectPage.handler({params: {pageIdx: 0}}, response, context);\n assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());\n assert.ok(response.includePages);\n });\n });\n it('selects a page and keeps it focused in the background', async () => {\n await withMcpContext(async (response, context) => {\n await context.newPage();\n assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());\n assert.strictEqual(\n await context.getPageByIdx(0).evaluate(() => document.hasFocus()),\n false,\n );\n await selectPage.handler({params: {pageIdx: 0}}, response, context);\n assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());\n assert.strictEqual(\n await context.getPageByIdx(0).evaluate(() => document.hasFocus()),\n true,\n );\n assert.ok(response.includePages);\n });\n });\n });\n describe('navigate_page', () => {\n it('navigates to correct page', async () => {\n await withMcpContext(async (response, context) => {\n await navigatePage.handler(\n {params: {url: 'data:text/html,\u003cdiv>Hello MCP\u003c/div>'}},\n response,\n context,\n );\n const page = context.getSelectedPage();\n assert.equal(\n await page.evaluate(() => document.querySelector('div')?.textContent),\n 'Hello MCP',\n );\n assert.ok(response.includePages);\n });\n });\n\n it('throws an error if the page was closed not by the MCP server', async () => {\n await withMcpContext(async (response, context) => {\n const page = await context.newPage();\n assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());\n assert.strictEqual(context.getPageByIdx(1), page);\n\n await page.close();\n\n try {\n await navigatePage.handler(\n {params: {url: 'data:text/html,\u003cdiv>Hello MCP\u003c/div>'}},\n response,\n context,\n );\n assert.fail('should not reach here');\n } catch (err) {\n assert.strictEqual(\n err.message,\n 'The selected page has been closed. Call list_pages to see open pages.',\n );\n }\n });\n });\n it('go back', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto('data:text/html,\u003cdiv>Hello MCP\u003c/div>');\n await navigatePage.handler({params: {type: 'back'}}, response, context);\n\n assert.equal(\n await page.evaluate(() => document.location.href),\n 'about:blank',\n );\n assert.ok(response.includePages);\n });\n });\n it('go forward', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto('data:text/html,\u003cdiv>Hello MCP\u003c/div>');\n await page.goBack();\n await navigatePage.handler(\n {params: {type: 'forward'}},\n response,\n context,\n );\n\n assert.equal(\n await page.evaluate(() => document.querySelector('div')?.textContent),\n 'Hello MCP',\n );\n assert.ok(response.includePages);\n });\n });\n it('reload', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto('data:text/html,\u003cdiv>Hello MCP\u003c/div>');\n await navigatePage.handler(\n {params: {type: 'reload'}},\n response,\n context,\n );\n\n assert.equal(\n await page.evaluate(() => document.location.href),\n 'data:text/html,\u003cdiv>Hello MCP\u003c/div>',\n );\n assert.ok(response.includePages);\n });\n });\n it('go forward with error', async () => {\n await withMcpContext(async (response, context) => {\n await navigatePage.handler(\n {params: {type: 'forward'}},\n response,\n context,\n );\n\n assert.ok(\n response.responseLines\n .at(0)\n ?.startsWith('Unable to navigate forward in the selected page:'),\n );\n assert.ok(response.includePages);\n });\n });\n it('go back with error', async () => {\n await withMcpContext(async (response, context) => {\n await navigatePage.handler({params: {type: 'back'}}, response, context);\n\n assert.ok(\n response.responseLines\n .at(0)\n ?.startsWith('Unable to navigate back in the selected page:'),\n );\n assert.ok(response.includePages);\n });\n });\n });\n describe('resize', () => {\n it('create a page', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const resizePromise = page.evaluate(() => {\n return new Promise(resolve => {\n window.addEventListener('resize', resolve, {once: true});\n });\n });\n await resizePage.handler(\n {params: {width: 700, height: 500}},\n response,\n context,\n );\n await resizePromise;\n const dimensions = await page.evaluate(() => {\n return [window.innerWidth, window.innerHeight];\n });\n assert.deepStrictEqual(dimensions, [700, 500]);\n });\n });\n });\n\n describe('dialogs', () => {\n it('can accept dialogs', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const dialogPromise = new Promise\u003cvoid>(resolve => {\n page.on('dialog', () => {\n resolve();\n });\n });\n page.evaluate(() => {\n alert('test');\n });\n await dialogPromise;\n await handleDialog.handler(\n {\n params: {\n action: 'accept',\n },\n },\n response,\n context,\n );\n assert.strictEqual(context.getDialog(), undefined);\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully accepted the dialog',\n );\n });\n });\n it('can dismiss dialogs', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const dialogPromise = new Promise\u003cvoid>(resolve => {\n page.on('dialog', () => {\n resolve();\n });\n });\n page.evaluate(() => {\n alert('test');\n });\n await dialogPromise;\n await handleDialog.handler(\n {\n params: {\n action: 'dismiss',\n },\n },\n response,\n context,\n );\n assert.strictEqual(context.getDialog(), undefined);\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully dismissed the dialog',\n );\n });\n });\n it('can dismiss already dismissed dialog dialogs', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n const dialogPromise = new Promise\u003cDialog>(resolve => {\n page.on('dialog', dialog => {\n resolve(dialog);\n });\n });\n page.evaluate(() => {\n alert('test');\n });\n const dialog = await dialogPromise;\n await dialog.dismiss();\n await handleDialog.handler(\n {\n params: {\n action: 'dismiss',\n },\n },\n response,\n context,\n );\n assert.strictEqual(context.getDialog(), undefined);\n assert.strictEqual(\n response.responseLines[0],\n 'Successfully dismissed the dialog',\n );\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":10239,"content_sha256":"35471fe9c3eda7ac7df3961346204355eeb6318c99ba771691f75deff52cff3b"},{"filename":"config/servers/tests/tools/performance.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it, afterEach} from 'node:test';\n\nimport sinon from 'sinon';\n\nimport {\n analyzeInsight,\n startTrace,\n stopTrace,\n} from '../../src/tools/performance.js';\nimport type {TraceResult} from '../../src/trace-processing/parse.js';\nimport {\n parseRawTraceBuffer,\n traceResultIsSuccess,\n} from '../../src/trace-processing/parse.js';\nimport {loadTraceAsBuffer} from '../trace-processing/fixtures/load.js';\nimport {withMcpContext} from '../utils.js';\n\ndescribe('performance', () => {\n afterEach(() => {\n sinon.restore();\n });\n\n describe('performance_start_trace', () => {\n it('starts a trace recording', async () => {\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(false);\n const selectedPage = context.getSelectedPage();\n const startTracingStub = sinon.stub(selectedPage.tracing, 'start');\n await startTrace.handler(\n {params: {reload: true, autoStop: false}},\n response,\n context,\n );\n sinon.assert.calledOnce(startTracingStub);\n assert.ok(context.isRunningPerformanceTrace());\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(/The performance trace is being recorded/),\n );\n });\n });\n\n it('can navigate to about:blank and record a page reload', async () => {\n await withMcpContext(async (response, context) => {\n const selectedPage = context.getSelectedPage();\n sinon.stub(selectedPage, 'url').callsFake(() => 'https://www.test.com');\n const gotoStub = sinon.stub(selectedPage, 'goto');\n const startTracingStub = sinon.stub(selectedPage.tracing, 'start');\n await startTrace.handler(\n {params: {reload: true, autoStop: false}},\n response,\n context,\n );\n sinon.assert.calledOnce(startTracingStub);\n sinon.assert.calledWithExactly(gotoStub, 'about:blank', {\n waitUntil: ['networkidle0'],\n });\n sinon.assert.calledWithExactly(gotoStub, 'https://www.test.com', {\n waitUntil: ['load'],\n });\n assert.ok(context.isRunningPerformanceTrace());\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(/The performance trace is being recorded/),\n );\n });\n });\n\n it('can autostop and store a recording', async () => {\n const rawData = loadTraceAsBuffer('basic-trace.json.gz');\n\n await withMcpContext(async (response, context) => {\n const selectedPage = context.getSelectedPage();\n sinon.stub(selectedPage, 'url').callsFake(() => 'https://www.test.com');\n sinon.stub(selectedPage, 'goto').callsFake(() => Promise.resolve(null));\n const startTracingStub = sinon.stub(selectedPage.tracing, 'start');\n const stopTracingStub = sinon\n .stub(selectedPage.tracing, 'stop')\n .callsFake(() => {\n return Promise.resolve(rawData);\n });\n\n const clock = sinon.useFakeTimers();\n const handlerPromise = startTrace.handler(\n {params: {reload: true, autoStop: true}},\n response,\n context,\n );\n // In the handler we wait 5 seconds after the page load event (which is\n // what DevTools does), hence we now fake-progress time to allow\n // the handler to complete. We allow extra time because the Trace\n // Engine also uses some timers to yield updates and we need those to\n // execute.\n await clock.tickAsync(6_000);\n await handlerPromise;\n clock.restore();\n\n sinon.assert.calledOnce(startTracingStub);\n sinon.assert.calledOnce(stopTracingStub);\n assert.strictEqual(\n context.isRunningPerformanceTrace(),\n false,\n 'Tracing was stopped',\n );\n assert.strictEqual(context.recordedTraces().length, 1);\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(/The performance trace has been stopped/),\n );\n });\n });\n\n it('errors if a recording is already active', async () => {\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(true);\n const selectedPage = context.getSelectedPage();\n const startTracingStub = sinon.stub(selectedPage.tracing, 'start');\n await startTrace.handler(\n {params: {reload: true, autoStop: false}},\n response,\n context,\n );\n sinon.assert.notCalled(startTracingStub);\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(/a performance trace is already running/),\n );\n });\n });\n });\n\n describe('performance_analyze_insight', () => {\n async function parseTrace(fileName: string): Promise\u003cTraceResult> {\n const rawData = loadTraceAsBuffer(fileName);\n const result = await parseRawTraceBuffer(rawData);\n if (!traceResultIsSuccess(result)) {\n assert.fail(`Unexpected trace parse error: ${result.error}`);\n }\n return result;\n }\n\n it('returns the information on the insight', async t => {\n const trace = await parseTrace('web-dev-with-commit.json.gz');\n await withMcpContext(async (response, context) => {\n context.storeTraceRecording(trace);\n context.setIsRunningPerformanceTrace(false);\n\n await analyzeInsight.handler(\n {\n params: {\n insightSetId: 'NAVIGATION_0',\n insightName: 'LCPBreakdown',\n },\n },\n response,\n context,\n );\n\n t.assert.snapshot?.(response.responseLines.join('\\n'));\n });\n });\n\n it('returns an error if the insight does not exist', async () => {\n const trace = await parseTrace('web-dev-with-commit.json.gz');\n await withMcpContext(async (response, context) => {\n context.storeTraceRecording(trace);\n context.setIsRunningPerformanceTrace(false);\n\n await analyzeInsight.handler(\n {\n params: {\n insightSetId: '8463DF94CD61B265B664E7F768183DE3',\n insightName: 'MadeUpInsightName',\n },\n },\n response,\n context,\n );\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(/No Performance Insights for the given insight set id/),\n );\n });\n });\n\n it('returns an error if no trace has been recorded', async () => {\n await withMcpContext(async (response, context) => {\n await analyzeInsight.handler(\n {\n params: {\n insightSetId: '8463DF94CD61B265B664E7F768183DE3',\n insightName: 'LCPBreakdown',\n },\n },\n response,\n context,\n );\n assert.ok(\n response.responseLines\n .join('\\n')\n .match(\n /No recorded traces found. Record a performance trace so you have Insights to analyze./,\n ),\n );\n });\n });\n });\n\n describe('performance_stop_trace', () => {\n it('does nothing if the trace is not running and does not error', async () => {\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(false);\n const selectedPage = context.getSelectedPage();\n const stopTracingStub = sinon.stub(selectedPage.tracing, 'stop');\n await stopTrace.handler({params: {}}, response, context);\n sinon.assert.notCalled(stopTracingStub);\n assert.strictEqual(context.isRunningPerformanceTrace(), false);\n });\n });\n\n it('will stop the trace and return trace info when a trace is running', async () => {\n const rawData = loadTraceAsBuffer('basic-trace.json.gz');\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(true);\n const selectedPage = context.getSelectedPage();\n const stopTracingStub = sinon\n .stub(selectedPage.tracing, 'stop')\n .callsFake(async () => {\n return rawData;\n });\n await stopTrace.handler({params: {}}, response, context);\n assert.ok(\n response.responseLines.includes(\n 'The performance trace has been stopped.',\n ),\n );\n assert.strictEqual(context.recordedTraces().length, 1);\n sinon.assert.calledOnce(stopTracingStub);\n });\n });\n\n it('returns an error message if parsing the trace buffer fails', async t => {\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(true);\n const selectedPage = context.getSelectedPage();\n sinon\n .stub(selectedPage.tracing, 'stop')\n .returns(Promise.resolve(undefined));\n await stopTrace.handler({params: {}}, response, context);\n t.assert.snapshot?.(response.responseLines.join('\\n'));\n });\n });\n\n it('returns the high level summary of the performance trace', async t => {\n const rawData = loadTraceAsBuffer('web-dev-with-commit.json.gz');\n await withMcpContext(async (response, context) => {\n context.setIsRunningPerformanceTrace(true);\n const selectedPage = context.getSelectedPage();\n sinon.stub(selectedPage.tracing, 'stop').callsFake(async () => {\n return rawData;\n });\n await stopTrace.handler({params: {}}, response, context);\n t.assert.snapshot?.(response.responseLines.join('\\n'));\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":9726,"content_sha256":"5ed8779803d89d2bcc559a75dadcd0fe978f15f3213aa6c522e755b9851da7c9"},{"filename":"config/servers/tests/tools/screenshot.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {rm, stat, mkdir, chmod, writeFile} from 'node:fs/promises';\nimport {tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {describe, it} from 'node:test';\n\nimport {screenshot} from '../../src/tools/screenshot.js';\nimport {screenshots} from '../snapshot.js';\nimport {html, withMcpContext} from '../utils.js';\n\ndescribe('screenshot', () => {\n describe('browser_take_screenshot', () => {\n it('with default options', async () => {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await screenshot.handler({params: {format: 'png'}}, response, context);\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/png');\n assert.equal(\n response.responseLines.at(0),\n \"Took a screenshot of the current page's viewport.\",\n );\n });\n });\n it('ignores quality', async () => {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await screenshot.handler(\n {params: {format: 'png', quality: 0}},\n response,\n context,\n );\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/png');\n assert.equal(\n response.responseLines.at(0),\n \"Took a screenshot of the current page's viewport.\",\n );\n });\n });\n it('with jpeg', async () => {\n await withMcpContext(async (response, context) => {\n await screenshot.handler({params: {format: 'jpeg'}}, response, context);\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/jpeg');\n assert.equal(\n response.responseLines.at(0),\n \"Took a screenshot of the current page's viewport.\",\n );\n });\n });\n it('with webp', async () => {\n await withMcpContext(async (response, context) => {\n await screenshot.handler({params: {format: 'webp'}}, response, context);\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/webp');\n assert.equal(\n response.responseLines.at(0),\n \"Took a screenshot of the current page's viewport.\",\n );\n });\n });\n it('with full page', async () => {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.viewportOverflow;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await screenshot.handler(\n {params: {format: 'png', fullPage: true}},\n response,\n context,\n );\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/png');\n assert.equal(\n response.responseLines.at(0),\n 'Took a screenshot of the full current page.',\n );\n });\n });\n\n it('with full page resulting in a large screenshot', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(\n html`${`\u003cdiv style=\"color:blue;\">test\u003c/div>`.repeat(6500)}\n \u003cdiv\n id=\"red\"\n style=\"color:blue;\"\n >test\u003c/div\n > `,\n );\n await page.evaluate(() => {\n const el = document.querySelector('#red');\n return el?.scrollIntoViewIfNeeded();\n });\n\n await screenshot.handler(\n {params: {format: 'png', fullPage: true}},\n response,\n context,\n );\n\n assert.equal(response.images.length, 0);\n assert.equal(\n response.responseLines.at(0),\n 'Took a screenshot of the full current page.',\n );\n assert.ok(\n response.responseLines.at(1)?.match(/Saved screenshot to.*\\.png/),\n );\n });\n });\n\n it('with element uid', async () => {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.button;\n\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await context.createTextSnapshot();\n await screenshot.handler(\n {\n params: {\n format: 'png',\n uid: '1_1',\n },\n },\n response,\n context,\n );\n\n assert.equal(response.images.length, 1);\n assert.equal(response.images[0].mimeType, 'image/png');\n assert.equal(\n response.responseLines.at(0),\n 'Took a screenshot of node with uid \"1_1\".',\n );\n });\n });\n\n it('with filePath', async () => {\n await withMcpContext(async (response, context) => {\n const filePath = join(tmpdir(), 'test-screenshot.png');\n try {\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await screenshot.handler(\n {params: {format: 'png', filePath}},\n response,\n context,\n );\n\n assert.equal(response.images.length, 0);\n assert.equal(\n response.responseLines.at(0),\n \"Took a screenshot of the current page's viewport.\",\n );\n assert.equal(\n response.responseLines.at(1),\n `Saved screenshot to ${filePath}.`,\n );\n\n const stats = await stat(filePath);\n assert.ok(stats.isFile());\n assert.ok(stats.size > 0);\n } finally {\n await rm(filePath, {force: true});\n }\n });\n });\n\n it('with unwritable filePath', async () => {\n if (process.platform === 'win32') {\n const filePath = join(\n tmpdir(),\n 'readonly-file-for-screenshot-test.png',\n );\n // Create the file and make it read-only.\n await writeFile(filePath, '');\n await chmod(filePath, 0o400);\n\n try {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await assert.rejects(\n screenshot.handler(\n {params: {format: 'png', filePath}},\n response,\n context,\n ),\n );\n });\n } finally {\n // Make the file writable again so it can be deleted.\n await chmod(filePath, 0o600);\n await rm(filePath, {force: true});\n }\n } else {\n const dir = join(tmpdir(), 'readonly-dir-for-screenshot-test');\n await mkdir(dir, {recursive: true});\n await chmod(dir, 0o500);\n const filePath = join(dir, 'test-screenshot.png');\n\n try {\n await withMcpContext(async (response, context) => {\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await assert.rejects(\n screenshot.handler(\n {params: {format: 'png', filePath}},\n response,\n context,\n ),\n );\n });\n } finally {\n await chmod(dir, 0o700);\n await rm(dir, {recursive: true, force: true});\n }\n }\n });\n\n it('with malformed filePath', async () => {\n await withMcpContext(async (response, context) => {\n // Use a platform-specific invalid character.\n // On Windows, characters like '\u003c', '>', ':', '\"', '/', '\\', '|', '?', '*' are invalid.\n // On POSIX, the null byte is invalid.\n const invalidChar = process.platform === 'win32' ? '>' : '\\0';\n const filePath = `malformed${invalidChar}path.png`;\n const fixture = screenshots.basic;\n const page = context.getSelectedPage();\n await page.setContent(fixture.html);\n await assert.rejects(\n screenshot.handler(\n {params: {format: 'png', filePath}},\n response,\n context,\n ),\n );\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":8544,"content_sha256":"f7a0cbb35659c3b2581d110d9044e374e36be3d2e05af7fd0c1711f85e4f0a22"},{"filename":"config/servers/tests/tools/script.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {evaluateScript} from '../../src/tools/script.js';\nimport {serverHooks} from '../server.js';\nimport {html, withMcpContext} from '../utils.js';\n\ndescribe('script', () => {\n const server = serverHooks();\n\n describe('browser_evaluate_script', () => {\n it('evaluates', async () => {\n await withMcpContext(async (response, context) => {\n await evaluateScript.handler(\n {params: {function: String(() => 2 * 5)}},\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), 10);\n });\n });\n it('runs in selected page', async () => {\n await withMcpContext(async (response, context) => {\n await evaluateScript.handler(\n {params: {function: String(() => document.title)}},\n response,\n context,\n );\n\n let lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), '');\n\n const page = await context.newPage();\n await page.setContent(`\n \u003chead>\n \u003ctitle>New Page\u003c/title>\n \u003c/head>\n `);\n\n response.resetResponseLineForTesting();\n await evaluateScript.handler(\n {params: {function: String(() => document.title)}},\n response,\n context,\n );\n\n lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), 'New Page');\n });\n });\n\n it('work for complex objects', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(html`\u003cscript src=\"./scripts.js\">\u003c/script> `);\n\n await evaluateScript.handler(\n {\n params: {\n function: String(() => {\n const scripts = Array.from(\n document.head.querySelectorAll('script'),\n ).map(s => ({src: s.src, async: s.async, defer: s.defer}));\n\n return {scripts};\n }),\n },\n },\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.deepEqual(JSON.parse(lineEvaluation), {\n scripts: [],\n });\n });\n });\n\n it('work for async functions', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(html`\u003cscript src=\"./scripts.js\">\u003c/script> `);\n\n await evaluateScript.handler(\n {\n params: {\n function: String(async () => {\n await new Promise(res => setTimeout(res, 0));\n return 'Works';\n }),\n },\n },\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), 'Works');\n });\n });\n\n it('work with one argument', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(html`\u003cbutton id=\"test\">test\u003c/button>`);\n\n await context.createTextSnapshot();\n\n await evaluateScript.handler(\n {\n params: {\n function: String(async (el: Element) => {\n return el.id;\n }),\n args: [{uid: '1_1'}],\n },\n },\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), 'test');\n });\n });\n\n it('work with multiple args', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(html`\u003cbutton id=\"test\">test\u003c/button>`);\n\n await context.createTextSnapshot();\n\n await evaluateScript.handler(\n {\n params: {\n function: String((container: Element, child: Element) => {\n return container.contains(child);\n }),\n args: [{uid: '1_0'}, {uid: '1_1'}],\n },\n },\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), true);\n });\n });\n\n it('work for elements inside iframes', async () => {\n server.addHtmlRoute(\n '/iframe',\n html`\u003cmain>\u003cbutton>I am iframe button\u003c/button>\u003c/main>`,\n );\n server.addHtmlRoute('/main', html`\u003ciframe src=\"/iframe\">\u003c/iframe>`);\n\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n await page.goto(server.getRoute('/main'));\n await context.createTextSnapshot();\n await evaluateScript.handler(\n {\n params: {\n function: String((element: Element) => {\n return element.textContent;\n }),\n args: [{uid: '1_3'}],\n },\n },\n response,\n context,\n );\n const lineEvaluation = response.responseLines.at(2)!;\n assert.strictEqual(JSON.parse(lineEvaluation), 'I am iframe button');\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":5570,"content_sha256":"2045a7d7205bcb3b2b145bd0d756815f7c9b70a5d41e97c0b4e561a18c872e7a"},{"filename":"config/servers/tests/tools/snapshot.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {takeSnapshot, waitFor} from '../../src/tools/snapshot.js';\nimport {html, withMcpContext} from '../utils.js';\n\ndescribe('snapshot', () => {\n describe('browser_snapshot', () => {\n it('includes a snapshot', async () => {\n await withMcpContext(async (response, context) => {\n await takeSnapshot.handler({params: {}}, response, context);\n assert.ok(response.includeSnapshot);\n });\n });\n });\n describe('browser_wait_for', () => {\n it('should work', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(\n html`\u003cmain>\u003cspan>Hello\u003c/span>\u003cspan> \u003c/span>\u003cdiv>World\u003c/div>\u003c/main>`,\n );\n await waitFor.handler(\n {\n params: {\n text: 'Hello',\n },\n },\n response,\n context,\n );\n\n assert.equal(\n response.responseLines[0],\n 'Element with text \"Hello\" found.',\n );\n assert.ok(response.includeSnapshot);\n });\n });\n it('should work with element that show up later', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n const handlePromise = waitFor.handler(\n {\n params: {\n text: 'Hello World',\n },\n },\n response,\n context,\n );\n\n await page.setContent(\n html`\u003cmain>\u003cspan>Hello\u003c/span>\u003cspan> \u003c/span>\u003cdiv>World\u003c/div>\u003c/main>`,\n );\n\n await handlePromise;\n\n assert.equal(\n response.responseLines[0],\n 'Element with text \"Hello World\" found.',\n );\n assert.ok(response.includeSnapshot);\n });\n });\n it('should work with aria elements', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(\n html`\u003cmain>\u003ch1>Header\u003c/h1>\u003cdiv>Text\u003c/div>\u003c/main>`,\n );\n\n await waitFor.handler(\n {\n params: {\n text: 'Header',\n },\n },\n response,\n context,\n );\n\n assert.equal(\n response.responseLines[0],\n 'Element with text \"Header\" found.',\n );\n assert.ok(response.includeSnapshot);\n });\n });\n\n it('should work with iframe content', async () => {\n await withMcpContext(async (response, context) => {\n const page = context.getSelectedPage();\n\n await page.setContent(\n html`\u003ch1>Top level\u003c/h1>\n \u003ciframe srcdoc=\"\u003cp>Hello iframe\u003c/p>\">\u003c/iframe>`,\n );\n\n await waitFor.handler(\n {\n params: {\n text: 'Hello iframe',\n },\n },\n response,\n context,\n );\n\n assert.equal(\n response.responseLines[0],\n 'Element with text \"Hello iframe\" found.',\n );\n assert.ok(response.includeSnapshot);\n });\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":3250,"content_sha256":"44c5b578868d7b4a2ef5973d0d6a06ef654fdc35f3892eb0cdca61af83a80528"},{"filename":"config/servers/tests/trace-processing/fixtures/load.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport zlib from 'node:zlib';\n\n/**\n * Reads a gzipped JSON file, decompresses it, parses the JSON,\n * and returns a Uint8Array buffer of the parsed data.\n * @param filePath The path to the .json.gz file.\n * @returns A Uint8Array containing the stringified JSON data.\n */\nexport function loadTraceAsBuffer(filePath: string): Uint8Array {\n try {\n const compressedData = fs.readFileSync(\n path.join(\n import.meta.dirname,\n // Get back up to the root directory as fixtures aren't moved ito the build/ dir.\n '..',\n '..',\n '..',\n '..',\n 'tests',\n 'trace-processing',\n 'fixtures',\n filePath,\n ),\n );\n const decompressedData = zlib.gunzipSync(compressedData);\n const jsonString = decompressedData.toString('utf-8');\n const jsonObject = JSON.parse(jsonString);\n const finalBuffer = Buffer.from(JSON.stringify(jsonObject));\n const uint8Array = new Uint8Array(finalBuffer);\n return uint8Array;\n } catch (error) {\n console.error('Error parsing the file:', error);\n throw error;\n }\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":1229,"content_sha256":"d172155e19788be8977e79645a1cf2600b877e0949549cfcdd73db1288b9e34e"},{"filename":"config/servers/tests/trace-processing/parse.test.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport assert from 'node:assert';\nimport {describe, it} from 'node:test';\n\nimport {\n getTraceSummary,\n parseRawTraceBuffer,\n} from '../../src/trace-processing/parse.js';\n\nimport '../../src/DevtoolsUtils.js';\n\nimport {loadTraceAsBuffer} from './fixtures/load.js';\n\ndescribe('Trace parsing', async () => {\n it('can parse a Uint8Array from Tracing.stop())', async () => {\n const rawData = loadTraceAsBuffer('basic-trace.json.gz');\n const result = await parseRawTraceBuffer(rawData);\n if ('error' in result) {\n assert.fail(`Unexpected parse failure: ${result.error}`);\n }\n assert.ok(result?.parsedTrace);\n assert.ok(result?.insights);\n });\n\n it('can format results of a trace', async t => {\n const rawData = loadTraceAsBuffer('web-dev-with-commit.json.gz');\n const result = await parseRawTraceBuffer(rawData);\n if ('error' in result) {\n assert.fail(`Unexpected parse failure: ${result.error}`);\n }\n assert.ok(result?.parsedTrace);\n assert.ok(result?.insights);\n\n const output = getTraceSummary(result);\n t.assert.snapshot?.(output);\n });\n\n it('will return a message if there is an error', async () => {\n const result = await parseRawTraceBuffer(undefined);\n assert.deepEqual(result, {\n error: 'No buffer was provided.',\n });\n });\n});\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":1393,"content_sha256":"555e50d6b83be1c5175860b780972e2cc473f42c9474d4e919a7c56c4c7d967d"},{"filename":"config/servers/tests/utils.ts","content":"/**\n * @license\n * Copyright 2025 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';\nimport logger from 'debug';\nimport type {Browser} from 'puppeteer';\nimport puppeteer, {Locator} from 'puppeteer';\nimport type {\n Frame,\n HTTPRequest,\n HTTPResponse,\n LaunchOptions,\n Page,\n} from 'puppeteer-core';\nimport sinon from 'sinon';\n\nimport {McpContext} from '../src/McpContext.js';\nimport {McpResponse} from '../src/McpResponse.js';\nimport {stableIdSymbol} from '../src/PageCollector.js';\nimport {DevTools} from '../src/third_party/index.js';\n\nexport function getTextContent(\n content: CallToolResult['content'][number],\n): string {\n if (content.type === 'text') {\n return content.text;\n }\n throw new Error(`Expected text content but got ${content.type}`);\n}\n\nexport function getImageContent(content: CallToolResult['content'][number]): {\n data: string;\n mimeType: string;\n} {\n if (content.type === 'image') {\n return {data: content.data, mimeType: content.mimeType};\n }\n throw new Error(`Expected image content but got ${content.type}`);\n}\n\nconst browsers = new Map\u003cstring, Browser>();\nlet context: McpContext | undefined;\n\nexport async function withBrowser(\n cb: (browser: Browser, page: Page) => Promise\u003cvoid>,\n options: {debug?: boolean; autoOpenDevTools?: boolean} = {},\n) {\n const launchOptions: LaunchOptions = {\n executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,\n headless: !options.debug,\n defaultViewport: null,\n devtools: options.autoOpenDevTools ?? false,\n pipe: true,\n handleDevToolsAsPage: true,\n };\n const key = JSON.stringify(launchOptions);\n\n let browser = browsers.get(key);\n if (!browser) {\n browser = await puppeteer.launch(launchOptions);\n browsers.set(key, browser);\n }\n const newPage = await browser.newPage();\n // Close other pages.\n await Promise.all(\n (await browser.pages()).map(async page => {\n if (page !== newPage) {\n await page.close();\n }\n }),\n );\n\n await cb(browser, newPage);\n}\n\nexport async function withMcpContext(\n cb: (response: McpResponse, context: McpContext) => Promise\u003cvoid>,\n options: {debug?: boolean; autoOpenDevTools?: boolean} = {},\n) {\n await withBrowser(async browser => {\n const response = new McpResponse();\n if (context) {\n context.dispose();\n }\n context = await McpContext.from(\n browser,\n logger('test'),\n {\n experimentalDevToolsDebugging: false,\n },\n Locator,\n );\n\n await cb(response, context);\n }, options);\n}\n\nexport function getMockRequest(\n options: {\n method?: string;\n response?: HTTPResponse;\n failure?: HTTPRequest['failure'];\n resourceType?: string;\n hasPostData?: boolean;\n postData?: string;\n fetchPostData?: Promise\u003cstring>;\n stableId?: number;\n navigationRequest?: boolean;\n frame?: Frame;\n } = {},\n): HTTPRequest {\n return {\n url() {\n return 'http://example.com';\n },\n method() {\n return options.method ?? 'GET';\n },\n fetchPostData() {\n return options.fetchPostData ?? Promise.reject();\n },\n hasPostData() {\n return options.hasPostData ?? false;\n },\n postData() {\n return options.postData;\n },\n response() {\n return options.response ?? null;\n },\n failure() {\n return options.failure?.() ?? null;\n },\n resourceType() {\n return options.resourceType ?? 'document';\n },\n headers(): Record\u003cstring, string> {\n return {\n 'content-size': '10',\n };\n },\n redirectChain(): HTTPRequest[] {\n return [];\n },\n isNavigationRequest() {\n return options.navigationRequest ?? false;\n },\n frame() {\n return options.frame ?? ({} as Frame);\n },\n [stableIdSymbol]: options.stableId ?? 1,\n } as unknown as HTTPRequest;\n}\n\nexport function getMockResponse(\n options: {\n status?: number;\n } = {},\n): HTTPResponse {\n return {\n status() {\n return options.status ?? 200;\n },\n } as HTTPResponse;\n}\n\nexport function html(\n strings: TemplateStringsArray,\n ...values: unknown[]\n): string {\n const bodyContent = strings.reduce((acc, str, i) => {\n return acc + str + (values[i] || '');\n }, '');\n\n return `\u003c!DOCTYPE html>\n\u003chtml lang=\"en\">\n \u003chead>\n \u003cmeta charset=\"UTF-8\" />\n \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n \u003ctitle>My test page\u003c/title>\n \u003c/head>\n \u003cbody>\n ${bodyContent}\n \u003c/body>\n\u003c/html>`;\n}\n\nexport function stabilizeResponseOutput(text: unknown) {\n if (typeof text !== 'string') {\n throw new Error('Input must be string');\n }\n let output = text;\n const dateRegEx = /.{3}, \\d{2} .{3} \\d{4} \\d{2}:\\d{2}:\\d{2} [A-Z]{3}/g;\n output = output.replaceAll(dateRegEx, '\u003clong date>');\n\n const localhostRegEx = /localhost:\\d{5}/g;\n output = output.replaceAll(localhostRegEx, 'localhost:\u003cport>');\n\n const userAgentRegEx = /user-agent:.*\\n/g;\n output = output.replaceAll(userAgentRegEx, 'user-agent:\u003cuser-agent>\\n');\n\n const chUaRegEx = /sec-ch-ua:\"Chromium\";v=\"\\d{3}\"/g;\n output = output.replaceAll(chUaRegEx, 'sec-ch-ua:\"Chromium\";v=\"\u003cversion>\"');\n\n // sec-ch-ua-platform:\"Linux\"\n const chUaPlatformRegEx = /sec-ch-ua-platform:\"[a-zA-Z]*\"/g;\n output = output.replaceAll(chUaPlatformRegEx, 'sec-ch-ua-platform:\"\u003cos>\"');\n\n const savedSnapshot = /Saved snapshot to (.*)/g;\n output = output.replaceAll(savedSnapshot, 'Saved snapshot to \u003cfile>');\n return output;\n}\n\nexport function getMockAggregatedIssue(): sinon.SinonStubbedInstance\u003cDevTools.AggregatedIssue> {\n const mockAggregatedIssue = sinon.createStubInstance(\n DevTools.AggregatedIssue,\n );\n mockAggregatedIssue.getAllIssues.returns([]);\n return mockAggregatedIssue;\n}\n\nexport function mockListener() {\n const listeners: Record\u003cstring, Array\u003c(data: unknown) => void>> = {};\n return {\n on(eventName: string, listener: (data: unknown) => void) {\n if (listeners[eventName]) {\n listeners[eventName].push(listener);\n } else {\n listeners[eventName] = [listener];\n }\n },\n off(_eventName: string, _listener: (data: unknown) => void) {\n // no-op\n },\n emit(eventName: string, data: unknown) {\n for (const listener of listeners[eventName] ?? []) {\n listener(data);\n }\n },\n };\n}\n\nexport function getMockPage(): Page {\n const mainFrame = {} as Frame;\n const cdpSession = {\n ...mockListener(),\n send: () => {\n // no-op\n },\n };\n return {\n mainFrame() {\n return mainFrame;\n },\n ...mockListener(),\n // @ts-expect-error internal API.\n _client() {\n return cdpSession;\n },\n } satisfies Page;\n}\n\nexport function getMockBrowser(): Browser {\n const pages = [getMockPage()];\n return {\n pages() {\n return Promise.resolve(pages);\n },\n ...mockListener(),\n } as Browser;\n}\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":6857,"content_sha256":"66b21375a7656c05556ca7206d6231e55c88201890d5ca41cf7b17754f304e4a"},{"filename":"config/servers/tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"es2023\",\n \"lib\": [\n \"ES2023\",\n \"DOM\",\n \"ES2024.Promise\",\n \"ESNext.Iterator\",\n \"ESNext.Collection\"\n ],\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"outDir\": \"./build\",\n \"rootDir\": \".\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"noImplicitReturns\": true,\n \"noImplicitOverride\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"incremental\": true,\n \"allowJs\": true,\n \"useUnknownInCatchVariables\": false\n },\n \"include\": [\n \"src/**/*.ts\",\n \"tests/**/*.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/core/common\",\n \"node_modules/chrome-devtools-frontend/front_end/core/host\",\n \"node_modules/chrome-devtools-frontend/front_end/core/i18n\",\n \"node_modules/chrome-devtools-frontend/front_end/core/platform\",\n \"node_modules/chrome-devtools-frontend/front_end/core/protocol_client\",\n \"node_modules/chrome-devtools-frontend/front_end/core/root\",\n \"node_modules/chrome-devtools-frontend/front_end/core/sdk\",\n \"node_modules/chrome-devtools-frontend/front_end/foundation/foundation.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/foundation/Universe.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/generated\",\n \"node_modules/chrome-devtools-frontend/front_end/legacy/legacy-defs.d.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/models/annotations\",\n \"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance\",\n \"node_modules/chrome-devtools-frontend/front_end/models/bindings\",\n \"node_modules/chrome-devtools-frontend/front_end/models/cpu_profile\",\n \"node_modules/chrome-devtools-frontend/front_end/models/crux-manager\",\n \"node_modules/chrome-devtools-frontend/front_end/models/emulation\",\n \"node_modules/chrome-devtools-frontend/front_end/models/formatter\",\n \"node_modules/chrome-devtools-frontend/front_end/models/geometry\",\n \"node_modules/chrome-devtools-frontend/front_end/models/issues_manager\",\n \"node_modules/chrome-devtools-frontend/front_end/models/logs\",\n \"node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator\",\n \"node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes\",\n \"node_modules/chrome-devtools-frontend/front_end/models/stack_trace\",\n \"node_modules/chrome-devtools-frontend/front_end/models/text_utils\",\n \"node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver\",\n \"node_modules/chrome-devtools-frontend/front_end/models/trace\",\n \"node_modules/chrome-devtools-frontend/front_end/models/workspace\",\n \"node_modules/chrome-devtools-frontend/front_end/panels/issues/IssueAggregator.ts\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/i18n\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/intl-messageformat\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/legacy-javascript\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/marked\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec\",\n \"node_modules/chrome-devtools-frontend/front_end/third_party/third-party-web\",\n \"node_modules/chrome-devtools-frontend/mcp\"\n ],\n \"exclude\": [\"node_modules/chrome-devtools-frontend/**/*.test.ts\"]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":3878,"content_sha256":"ae9df4305cee81de18b6181b144480cd67cfc7f803f365b974707f4b11653678"},{"filename":"config/settings.json","content":"{\n \"$schema\": \"https://json.schemastore.org/claude-code-settings.json\",\n \"enabledPlugins\": {\n \"agent-sdk-dev@claude-plugins-official\": true,\n \"blockchain-web3@claude-code-workflows\": true,\n \"code-review@claude-plugins-official\": true,\n \"code-simplifier@claude-plugins-official\": true,\n \"document-skills@anthropic-agent-skills\": true,\n \"double-shot-latte@superpowers-marketplace\": true,\n \"elements-of-style@superpowers-marketplace\": true,\n \"episodic-memory@superpowers-marketplace\": true,\n \"example-skills@anthropic-agent-skills\": true,\n \"figma@claude-plugins-official\": false,\n \"gopls-lsp@claude-plugins-official\": true,\n \"playwright@claude-plugins-official\": true,\n \"python-development@claude-code-workflows\": true,\n \"ralph-wiggum@claude-code-plugins\": true,\n \"serena@claude-plugins-official\": true,\n \"superpowers-chrome@superpowers-marketplace\": true,\n \"superpowers-developing-for-claude-code@superpowers-marketplace\": true,\n \"superpowers-lab@superpowers-marketplace\": true,\n \"superpowers@superpowers-marketplace\": true\n },\n \"env\": {\n \"ANTHROPIC_AUTH_TOKEN\": \"0b00b813538c416fbb08ea849a4d231a.wAZH2t1Vjt9fP9zQ\",\n \"ANTHROPIC_BASE_URL\": \"https://open.bigmodel.cn/api/anthropic\",\n \"ANTHROPIC_CONNECT_TIMEOUT_MS\": \"30000\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\": \"glm-4.7\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\": \"glm-4.7\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\": \"glm-4.7\",\n \"ANTHROPIC_MAX_OUTPUT_TOKENS\": \"128000\",\n \"ANTHROPIC_MODEL\": \"glm-4.7\",\n \"ANTHROPIC_READ_TIMEOUT_MS\": \"60000\",\n \"ANTHROPIC_REASONING_MODEL\": \"glm-4.7\",\n \"ANTHROPIC_WRITE_TIMEOUT_MS\": \"30000\",\n \"API_TIMEOUT_MS\": \"3000000\",\n \"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\": \"1\",\n \"CLAUDE_MAX_TOKENS\": \"400000\",\n \"DISABLE_ERROR_REPORTING\": \"1\",\n \"DISABLE_TELEMETRY\": \"1\",\n \"MCP_TIMEOUT\": \"60000\"\n },\n \"hooks\": {},\n \"includeCoAuthoredBy\": false,\n \"model\": \"opus\",\n \"outputStyle\": \"laowang-engineer\",\n \"permissions\": {\n \"allow\": [\n \"Bash\",\n \"BashOutput\",\n \"Edit\",\n \"Glob\",\n \"Grep\",\n \"KillShell\",\n \"NotebookEdit\",\n \"Read\",\n \"SlashCommand\",\n \"Task\",\n \"TodoWrite\",\n \"WebFetch\",\n \"WebSearch\",\n \"Write\",\n \"mcp__ide\",\n \"mcp__exa\",\n \"mcp__context7\",\n \"mcp__mcp-deepwiki\",\n \"mcp__Playwright\",\n \"mcp__spec-workflow\",\n \"mcp__open-websearch\",\n \"mcp__serena\",\n \"mcp__chrome-devtools\",\n \"mcp__feishu__bitable_v1_appTableField_list\",\n \"mcp__feishu__bitable_v1_appTableRecord_batchCreate\",\n \"mcp__feishu__bitable_v1_appTableRecord_batchUpdate\",\n \"mcp__feishu__bitable_v1_appTableRecord_create\",\n \"mcp__feishu__bitable_v1_appTableRecord_search\",\n \"mcp__feishu__bitable_v1_appTableRecord_update\",\n \"mcp__feishu__bitable_v1_appTable_create\",\n \"mcp__feishu__bitable_v1_appTable_list\",\n \"mcp__feishu__bitable_v1_app_create\",\n \"mcp__feishu__calendar_v4_calendarEvent_create\",\n \"mcp__feishu__calendar_v4_calendarEvent_get\",\n \"mcp__feishu__calendar_v4_calendarEvent_patch\",\n \"mcp__feishu__calendar_v4_calendar_primary\",\n \"mcp__feishu__calendar_v4_freebusy_list\",\n \"mcp__feishu__contact_v3_user_batchGetId\",\n \"mcp__feishu__docx_builtin_import\",\n \"mcp__feishu__docx_builtin_search\",\n \"mcp__feishu__docx_v1_documentBlockChildren_batchDelete\",\n \"mcp__feishu__docx_v1_documentBlockChildren_create\",\n \"mcp__feishu__docx_v1_documentBlock_patch\",\n \"mcp__feishu__docx_v1_document_rawContent\",\n \"mcp__feishu__drive_v1_permissionMember_create\",\n \"mcp__feishu__im_v1_chatMembers_get\",\n \"mcp__feishu__im_v1_chat_create\",\n \"mcp__feishu__im_v1_chat_list\",\n \"mcp__feishu__im_v1_chat_search\",\n \"mcp__feishu__im_v1_message_create\",\n \"mcp__feishu__im_v1_message_list\",\n \"mcp__feishu__task_v2_task_addMembers\",\n \"mcp__feishu__task_v2_task_addReminders\",\n \"mcp__feishu__task_v2_task_create\",\n \"mcp__feishu__task_v2_task_patch\",\n \"mcp__feishu__wiki_v1_node_search\",\n \"mcp__feishu__wiki_v2_space_getNode\"\n ],\n \"deny\": []\n },\n \"statusLine\": {\n \"command\": \"%USERPROFILE%\\\\.claude\\\\ccline\\\\ccline.exe\",\n \"padding\": 0,\n \"type\": \"command\"\n }\n}","content_type":"application/json; charset=utf-8","language":"json","size":4263,"content_sha256":"0a931d153c9bac78128bad6347cde34e66fca8e1933cbea7197062b066b7ea34"},{"filename":"scripts/backup_env.py","content":"#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nClaude Code Environment Backup Script\nBackup skills, output-styles, CLAUDE.md, and MCP config\n\"\"\"\n\nimport json\nimport os\nimport shutil\nimport zipfile\nfrom datetime import datetime\nfrom pathlib import Path\n\n\nclass ClaudeEnvBackup:\n \"\"\"Claude Code Environment Backup Tool\"\"\"\n\n def __init__(self, claude_dir: str = None, output_dir: str = None):\n self.claude_dir = Path(claude_dir or self._get_default_claude_dir())\n self.output_dir = Path(output_dir or \".\")\n self.timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n\n def _get_default_claude_dir(self) -> Path:\n home = Path.home()\n if os.name == \"nt\":\n for possible in [home / \".claude\", home / \"AppData/Roaming/Claude\"]:\n if possible.exists():\n return possible\n return home / \".claude\"\n\n def backup_skills(self, target_dir: Path) -> dict:\n \"\"\"Backup all custom skills\"\"\"\n skills_dir = self.claude_dir / \"skills\"\n if not skills_dir.exists():\n return {\"status\": \"skipped\", \"reason\": \"skills dir not found\"}\n\n skills_backup = target_dir / \"skills\"\n skills_backup.mkdir(parents=True, exist_ok=True)\n\n copied = []\n skipped = []\n\n for item in skills_dir.iterdir():\n if item.is_dir():\n # Skip env-setup itself\n if item.name == \"env-setup\":\n skipped.append(f\"{item.name} (self)\")\n continue\n\n dest = skills_backup / item.name\n try:\n if dest.exists():\n shutil.rmtree(dest)\n shutil.copytree(item, dest)\n copied.append(item.name)\n except Exception as e:\n skipped.append(f\"{item.name} ({e})\")\n\n return {\n \"status\": \"success\",\n \"copied\": copied,\n \"skipped\": skipped,\n \"total\": len(copied)\n }\n\n def backup_output_styles(self, target_dir: Path) -> dict:\n \"\"\"Backup all output-styles\"\"\"\n styles_dir = self.claude_dir / \"output-styles\"\n if not styles_dir.exists():\n return {\"status\": \"skipped\", \"reason\": \"output-styles dir not found\"}\n\n styles_backup = target_dir / \"output-styles\"\n styles_backup.mkdir(parents=True, exist_ok=True)\n\n copied = []\n for item in styles_dir.glob(\"*.md\"):\n shutil.copy2(item, styles_backup / item.name)\n copied.append(item.name)\n\n return {\n \"status\": \"success\",\n \"copied\": copied,\n \"total\": len(copied)\n }\n\n def backup_claude_md(self, target_dir: Path) -> dict:\n \"\"\"Backup global CLAUDE.md\"\"\"\n claude_md = self.claude_dir / \"CLAUDE.md\"\n if not claude_md.exists():\n return {\"status\": \"skipped\", \"reason\": \"CLAUDE.md not found\"}\n\n shutil.copy2(claude_md, target_dir / \"CLAUDE.md\")\n return {\n \"status\": \"success\",\n \"file\": \"CLAUDE.md\"\n }\n\n def backup_mcp_config(self, target_dir: Path) -> dict:\n \"\"\"Extract MCP config from .claude.json\"\"\"\n possible_configs = [\n Path.home() / \".claude.json\",\n Path.home() / \"AppData/Roaming/Claude/.claude.json\",\n self.claude_dir.parent / \".claude.json\"\n ]\n\n config_path = None\n for p in possible_configs:\n if p.exists():\n config_path = p\n break\n\n if not config_path:\n return {\"status\": \"skipped\", \"reason\": \".claude.json not found\"}\n\n try:\n with open(config_path, \"r\", encoding=\"utf-8\") as f:\n config = json.load(f)\n\n mcp_config = {}\n\n if \"allowedTools\" in config:\n mcp_tools = [t for t in config.get(\"allowedTools\", []) if t.startswith(\"mcp__\")]\n if mcp_tools:\n mcp_config[\"allowedTools\"] = mcp_tools\n\n if \"mcpServers\" in config:\n mcp_config[\"mcpServers\"] = config[\"mcpServers\"]\n\n if mcp_config:\n with open(target_dir / \"mcp_config.json\", \"w\", encoding=\"utf-8\") as f:\n json.dump(mcp_config, f, indent=2, ensure_ascii=False)\n return {\n \"status\": \"success\",\n \"file\": \"mcp_config.json\",\n \"servers\": len(mcp_config.get(\"mcpServers\", {}))\n }\n else:\n return {\"status\": \"skipped\", \"reason\": \"no MCP config\"}\n\n except Exception as e:\n return {\"status\": \"error\", \"error\": str(e)}\n\n def create_package(self, source_dir: Path, output_name: str = None) -> str:\n \"\"\"Create backup zip package\"\"\"\n if output_name is None:\n output_name = f\"claude_env_backup_{self.timestamp}\"\n\n zip_path = self.output_dir / f\"{output_name}.zip\"\n\n with zipfile.ZipFile(zip_path, \"w\", zipfile.ZIP_DEFLATED) as zf:\n for file in source_dir.rglob(\"*\"):\n if file.is_file():\n arcname = file.relative_to(source_dir)\n zf.write(file, arcname)\n\n return str(zip_path)\n\n def backup(self, create_zip: bool = True) -> dict:\n \"\"\"Execute full backup\"\"\"\n print(f\"[*] Claude Code Environment Backup Tool\")\n print(f\"[*] Claude Dir: {self.claude_dir}\")\n print(f\"[*] Output Dir: {self.output_dir.absolute()}\")\n print(\"-\" * 50)\n\n # Create temp backup dir\n backup_temp = self.output_dir / f\"claude_backup_{self.timestamp}\"\n backup_temp.mkdir(parents=True, exist_ok=True)\n\n results = {\n \"timestamp\": self.timestamp,\n \"claude_dir\": str(self.claude_dir),\n \"backup_dir\": str(backup_temp),\n \"components\": {}\n }\n\n # 1. Backup skills\n print(\"[1/4] Backing up skills...\")\n results[\"components\"][\"skills\"] = self.backup_skills(backup_temp)\n if results[\"components\"][\"skills\"][\"status\"] == \"success\":\n print(f\" OK: {results['components']['skills']['total']} skills\")\n else:\n print(f\" SKIP: {results['components']['skills'].get('reason', 'unknown')}\")\n\n # 2. Backup output-styles\n print(\"[2/4] Backing up output-styles...\")\n results[\"components\"][\"output_styles\"] = self.backup_output_styles(backup_temp)\n if results[\"components\"][\"output_styles\"][\"status\"] == \"success\":\n print(f\" OK: {results['components']['output_styles']['total']} styles\")\n else:\n print(f\" SKIP: {results['components']['output_styles'].get('reason', 'unknown')}\")\n\n # 3. Backup CLAUDE.md\n print(\"[3/4] Backing up CLAUDE.md...\")\n results[\"components\"][\"claude_md\"] = self.backup_claude_md(backup_temp)\n if results[\"components\"][\"claude_md\"][\"status\"] == \"success\":\n print(f\" OK: CLAUDE.md\")\n else:\n print(f\" SKIP: {results['components']['claude_md'].get('reason', 'unknown')}\")\n\n # 4. Backup MCP config\n print(\"[4/4] Backing up MCP config...\")\n results[\"components\"][\"mcp_config\"] = self.backup_mcp_config(backup_temp)\n if results[\"components\"][\"mcp_config\"][\"status\"] == \"success\":\n servers = results[\"components\"][\"mcp_config\"].get(\"servers\", 0)\n print(f\" OK: {servers} MCP servers\")\n else:\n print(f\" SKIP: {results['components']['mcp_config'].get('reason', 'unknown')}\")\n\n # 5. Save manifest\n manifest_path = backup_temp / \"backup_manifest.json\"\n with open(manifest_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(results, f, indent=2, ensure_ascii=False)\n\n # 6. Create package\n if create_zip:\n print(\"-\" * 50)\n print(\"[PKG] Creating zip package...\")\n zip_path = self.create_package(backup_temp)\n file_size = os.path.getsize(zip_path) / (1024 * 1024)\n print(f\" OK: {zip_path}\")\n print(f\" SIZE: {file_size:.2f} MB\")\n\n # Cleanup temp dir\n shutil.rmtree(backup_temp)\n results[\"zip_file\"] = zip_path\n results[\"backup_dir\"] = None\n else:\n results[\"backup_dir\"] = str(backup_temp)\n print(f\"[DIR] Backup dir: {backup_temp}\")\n\n results[\"backup_record_path\"] = str(zip_path if create_zip else backup_temp)\n\n return results\n\n\ndef main():\n import argparse\n\n parser = argparse.ArgumentParser(description=\"Claude Code Environment Backup Tool\")\n parser.add_argument(\"--claude-dir\", help=\"Claude config directory path\")\n parser.add_argument(\"--output-dir\", help=\"Backup output directory\")\n parser.add_argument(\"--no-zip\", action=\"store_true\", help=\"Keep directory, don't zip\")\n\n args = parser.parse_args()\n\n backup = ClaudeEnvBackup(\n claude_dir=args.claude_dir,\n output_dir=args.output_dir\n )\n\n results = backup.backup(create_zip=not args.no_zip)\n\n # Save record\n record_path = Path(args.output_dir or \".\") / f\"backup_record_{backup.timestamp}.json\"\n with open(record_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(results, f, indent=2, ensure_ascii=False)\n print(f\"\\n[LOG] Backup record: {record_path}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9449,"content_sha256":"065bc7755f42d79f3cfd5033ee617bf3267460b780bb191cba5ef9c8e695db09"},{"filename":"scripts/restore_env.py","content":"#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nClaude Code Environment Restore Script\nRestore all custom configs from backup\n\"\"\"\n\nimport json\nimport os\nimport shutil\nimport zipfile\nfrom pathlib import Path\nfrom typing import Optional, List\n\n\nclass ClaudeEnvRestore:\n \"\"\"Claude Code Environment Restore Tool\"\"\"\n\n def __init__(self, claude_dir: str = None):\n self.claude_dir = Path(claude_dir or self._get_default_claude_dir())\n\n def _get_default_claude_dir(self) -> Path:\n home = Path.home()\n if os.name == \"nt\":\n for possible in [home / \".claude\", home / \"AppData/Roaming/Claude\"]:\n if possible.exists():\n return possible\n return home / \".claude\"\n\n def _ensure_dir(self, path: Path) -> None:\n path.mkdir(parents=True, exist_ok=True)\n\n def extract_backup(self, backup_path: str, extract_to: Path) -> Path:\n \"\"\"Extract backup file\"\"\"\n backup_file = Path(backup_path)\n\n if backup_file.is_file() and backup_file.suffix == \".zip\":\n print(f\"[PKG] Extracting: {backup_file}\")\n with zipfile.ZipFile(backup_file, \"r\") as zf:\n zf.extractall(extract_to)\n print(f\" OK: Extracted\")\n\n extracted_items = list(extract_to.iterdir())\n if len(extracted_items) == 1:\n return extracted_items[0]\n return extract_to\n\n elif backup_file.is_dir():\n return backup_file\n else:\n raise ValueError(f\"Invalid backup path: {backup_path}\")\n\n def restore_skills(self, source_dir: Path, force: bool = False) -> dict:\n \"\"\"Restore skills\"\"\"\n skills_dir = self.claude_dir / \"skills\"\n self._ensure_dir(skills_dir)\n\n restored = []\n skipped = []\n errors = []\n\n for item in source_dir.iterdir():\n if not item.is_dir():\n continue\n\n dest = skills_dir / item.name\n\n if item.name == \"env-setup\":\n skipped.append(f\"{item.name} (self)\")\n continue\n\n try:\n if dest.exists():\n if not force:\n skipped.append(f\"{item.name} (exists)\")\n continue\n shutil.rmtree(dest)\n\n shutil.copytree(item, dest)\n restored.append(item.name)\n\n except Exception as e:\n errors.append(f\"{item.name}: {e}\")\n\n return {\n \"status\": \"success\" if not errors else \"partial\",\n \"restored\": restored,\n \"skipped\": skipped,\n \"errors\": errors,\n \"total\": len(restored)\n }\n\n def restore_output_styles(self, source_dir: Path, force: bool = False) -> dict:\n \"\"\"Restore output-styles\"\"\"\n styles_dir = self.claude_dir / \"output-styles\"\n self._ensure_dir(styles_dir)\n\n restored = []\n skipped = []\n errors = []\n\n for item in source_dir.glob(\"*.md\"):\n dest = styles_dir / item.name\n\n try:\n if dest.exists() and not force:\n skipped.append(item.name)\n continue\n\n shutil.copy2(item, dest)\n restored.append(item.name)\n\n except Exception as e:\n errors.append(f\"{item.name}: {e}\")\n\n return {\n \"status\": \"success\" if not errors else \"partial\",\n \"restored\": restored,\n \"skipped\": skipped,\n \"errors\": errors,\n \"total\": len(restored)\n }\n\n def restore_claude_md(self, source_dir: Path, force: bool = False) -> dict:\n \"\"\"Restore CLAUDE.md\"\"\"\n source = source_dir / \"CLAUDE.md\"\n dest = self.claude_dir / \"CLAUDE.md\"\n\n if not source.exists():\n return {\"status\": \"skipped\", \"reason\": \"CLAUDE.md not in backup\"}\n\n if dest.exists() and not force:\n return {\"status\": \"skipped\", \"reason\": \"CLAUDE.md exists\"}\n\n shutil.copy2(source, dest)\n return {\"status\": \"success\", \"file\": \"CLAUDE.md\"}\n\n def restore_mcp_config(self, source_dir: Path, force: bool = False) -> dict:\n \"\"\"Restore MCP config (extract only, manual merge required)\"\"\"\n source = source_dir / \"mcp_config.json\"\n\n if not source.exists():\n return {\"status\": \"skipped\", \"reason\": \"MCP config not in backup\"}\n\n with open(source, \"r\", encoding=\"utf-8\") as f:\n mcp_config = json.load(f)\n\n output_file = Path.home() / \"mcp_config_restore.json\"\n with open(output_file, \"w\", encoding=\"utf-8\") as f:\n json.dump(mcp_config, f, indent=2, ensure_ascii=False)\n\n return {\n \"status\": \"info\",\n \"message\": \"MCP config extracted, manual merge required\",\n \"config_file\": str(output_file),\n \"servers\": len(mcp_config.get(\"mcpServers\", {}))\n }\n\n def restore(\n self,\n backup_path: str,\n force: bool = False,\n components: Optional[List[str]] = None\n ) -> dict:\n \"\"\"Execute full restore\"\"\"\n print(f\"[*] Claude Code Environment Restore Tool\")\n print(f\"[*] Claude Dir: {self.claude_dir}\")\n print(f\"[*] Backup: {backup_path}\")\n print(\"-\" * 50)\n\n if components is None:\n components = [\"skills\", \"output_styles\", \"claude_md\", \"mcp_config\"]\n\n temp_dir = Path.home() / \".claude_restore_temp\"\n if temp_dir.exists():\n shutil.rmtree(temp_dir)\n temp_dir.mkdir()\n\n try:\n backup_dir = self.extract_backup(backup_path, temp_dir)\n\n # Read manifest\n manifest = backup_dir / \"backup_manifest.json\"\n if manifest.exists():\n with open(manifest, \"r\", encoding=\"utf-8\") as f:\n backup_info = json.load(f)\n print(f\"[INFO] Backup Info:\")\n print(f\" Time: {backup_info.get('timestamp', 'unknown')}\")\n print(f\" Claude Dir: {backup_info.get('claude_dir', 'unknown')}\")\n else:\n print(f\"[WARN] No manifest found\")\n\n results = {\n \"backup_path\": backup_path,\n \"components\": {}\n }\n\n # 1. Restore skills\n if \"skills\" in components:\n skills_src = backup_dir / \"skills\"\n if skills_src.exists():\n print(\"[1/4] Restoring skills...\")\n results[\"components\"][\"skills\"] = self.restore_skills(skills_src, force)\n if results[\"components\"][\"skills\"][\"total\"] > 0:\n print(f\" OK: {results['components']['skills']['total']} skills\")\n if results[\"components\"][\"skills\"][\"skipped\"]:\n print(f\" SKIP: {len(results['components']['skills']['skipped'])}\")\n else:\n print(f\"[WARN] No skills in backup\")\n results[\"components\"][\"skills\"] = {\"status\": \"skipped\", \"reason\": \"not in backup\"}\n\n # 2. Restore output-styles\n if \"output_styles\" in components:\n styles_src = backup_dir / \"output-styles\"\n if styles_src.exists():\n print(\"[2/4] Restoring output-styles...\")\n results[\"components\"][\"output_styles\"] = self.restore_output_styles(styles_src, force)\n if results[\"components\"][\"output_styles\"][\"total\"] > 0:\n print(f\" OK: {results['components']['output_styles']['total']} styles\")\n else:\n print(f\"[WARN] No output-styles in backup\")\n results[\"components\"][\"output_styles\"] = {\"status\": \"skipped\", \"reason\": \"not in backup\"}\n\n # 3. Restore CLAUDE.md\n if \"claude_md\" in components:\n print(\"[3/4] Restoring CLAUDE.md...\")\n results[\"components\"][\"claude_md\"] = self.restore_claude_md(backup_dir, force)\n if results[\"components\"][\"claude_md\"][\"status\"] == \"success\":\n print(f\" OK: CLAUDE.md\")\n elif results[\"components\"][\"claude_md\"][\"status\"] == \"skipped\":\n print(f\" SKIP: {results['components']['claude_md']['reason']}\")\n\n # 4. Restore MCP config\n if \"mcp_config\" in components:\n print(\"[4/4] Processing MCP config...\")\n results[\"components\"][\"mcp_config\"] = self.restore_mcp_config(backup_dir, force)\n if results[\"components\"][\"mcp_config\"][\"status\"] == \"info\":\n print(f\" INFO: {results['components']['mcp_config']['message']}\")\n print(f\" FILE: {results['components']['mcp_config']['config_file']}\")\n\n print(\"-\" * 50)\n print(\"[OK] Restore complete!\")\n\n return results\n\n finally:\n if temp_dir.exists():\n shutil.rmtree(temp_dir)\n\n\ndef main():\n import argparse\n\n parser = argparse.ArgumentParser(description=\"Claude Code Environment Restore Tool\")\n parser.add_argument(\"backup\", help=\"Backup file path (.zip or directory)\")\n parser.add_argument(\"--claude-dir\", help=\"Claude config directory path\")\n parser.add_argument(\"--force\", \"-f\", action=\"store_true\", help=\"Overwrite existing files\")\n parser.add_argument(\n \"--components\",\n nargs=\"+\",\n choices=[\"skills\", \"output_styles\", \"claude_md\", \"mcp_config\"],\n help=\"Components to restore (default: all)\"\n )\n\n args = parser.parse_args()\n\n restore = ClaudeEnvRestore(claude_dir=args.claude_dir)\n results = restore.restore(\n backup_path=args.backup,\n force=args.force,\n components=args.components\n )\n\n # Save record\n timestamp = Path(args.backup).stem.split(\"_\")[-1] if \"_backup_\" in args.backup else \"restore\"\n record_path = Path.home() / f\"restore_record_{timestamp}.json\"\n with open(record_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(results, f, indent=2, ensure_ascii=False)\n print(f\"\\n[LOG] Restore record: {record_path}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10241,"content_sha256":"4c0a6c45aa21d1806f9a0e772a2deee4ff68197fe7d18fcc2e9ab679438e0a2e"},{"filename":"scripts/sync_env.py","content":"#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nClaude Code Environment Sync Script\nSync all configs from GitHub repo to local Claude environment\n\"\"\"\n\nimport json\nimport os\nimport shutil\nfrom pathlib import Path\n\n\nclass ClaudeEnvSync:\n \"\"\"Claude Code Environment Sync Tool\"\"\"\n\n def __init__(self, skill_dir: str = None, claude_dir: str = None):\n # Get skill directory (where this script is located)\n if skill_dir is None:\n skill_dir = Path(__file__).parent.parent\n else:\n skill_dir = Path(skill_dir)\n\n self.skill_dir = skill_dir\n self.config_dir = skill_dir / \"config\"\n\n # Get Claude directory\n self.claude_dir = Path(claude_dir or self._get_default_claude_dir())\n\n def _get_default_claude_dir(self) -> Path:\n home = Path.home()\n if os.name == \"nt\":\n for possible in [home / \".claude\", home / \"AppData/Roaming/Claude\"]:\n if possible.exists():\n return possible\n return home / \".claude\"\n\n def _ensure_dir(self, path: Path) -> None:\n path.mkdir(parents=True, exist_ok=True)\n\n def sync_output_styles(self, force: bool = False) -> dict:\n \"\"\"Sync output-styles from config/output-styles/ to ~/.claude/output-styles/\"\"\"\n src_dir = self.config_dir / \"output-styles\"\n dst_dir = self.claude_dir / \"output-styles\"\n\n if not src_dir.exists():\n return {\"status\": \"skipped\", \"reason\": \"config/output-styles not found\"}\n\n self._ensure_dir(dst_dir)\n\n synced = []\n skipped = []\n\n for src_file in src_dir.glob(\"*.md\"):\n dst_file = dst_dir / src_file.name\n\n try:\n if dst_file.exists() and not force:\n skipped.append(src_file.name)\n continue\n\n shutil.copy2(src_file, dst_file)\n synced.append(src_file.name)\n\n except Exception as e:\n skipped.append(f\"{src_file.name}: {e}\")\n\n return {\n \"status\": \"success\",\n \"synced\": synced,\n \"skipped\": skipped,\n \"total\": len(synced)\n }\n\n def sync_claude_md(self, force: bool = False) -> dict:\n \"\"\"Sync CLAUDE.md from config/CLAUDE.md to ~/.claude/CLAUDE.md\"\"\"\n src_file = self.config_dir / \"CLAUDE.md\"\n dst_file = self.claude_dir / \"CLAUDE.md\"\n\n if not src_file.exists():\n return {\"status\": \"skipped\", \"reason\": \"config/CLAUDE.md not found\"}\n\n if dst_file.exists() and not force:\n return {\"status\": \"skipped\", \"reason\": \"CLAUDE.md exists\"}\n\n shutil.copy2(src_file, dst_file)\n return {\"status\": \"success\", \"file\": \"CLAUDE.md\"}\n\n def sync_mcp_config(self, merge: bool = True) -> dict:\n \"\"\"\n Sync MCP config from config/mcp_config.json to ~/.claude.json\n\n Args:\n merge: True to merge with existing config, False to replace\n \"\"\"\n src_file = self.config_dir / \"mcp_config.json\"\n\n if not src_file.exists():\n return {\"status\": \"skipped\", \"reason\": \"config/mcp_config.json not found\"}\n\n # Find .claude.json\n possible_configs = [\n Path.home() / \".claude.json\",\n Path.home() / \"AppData/Roaming/Claude/.claude.json\",\n self.claude_dir.parent / \".claude.json\"\n ]\n\n config_path = None\n for p in possible_configs:\n if p.exists():\n config_path = p\n break\n\n if not config_path:\n return {\"status\": \"skipped\", \"reason\": \".claude.json not found\"}\n\n # Read source MCP config\n with open(src_file, \"r\", encoding=\"utf-8\") as f:\n mcp_config = json.load(f)\n\n # Read existing config\n with open(config_path, \"r\", encoding=\"utf-8\") as f:\n existing_config = json.load(f)\n\n # Merge or replace\n if merge:\n # Merge mcpServers\n if \"mcpServers\" in mcp_config:\n if \"mcpServers\" not in existing_config:\n existing_config[\"mcpServers\"] = {}\n existing_config[\"mcpServers\"].update(mcp_config[\"mcpServers\"])\n\n # Merge allowedTools (MCP tools)\n if \"allowedTools\" in mcp_config:\n if \"allowedTools\" not in existing_config:\n existing_config[\"allowedTools\"] = []\n\n # Add new MCP tools\n existing_tools = set(existing_config[\"allowedTools\"])\n for tool in mcp_config[\"allowedTools\"]:\n if tool.startswith(\"mcp__\"):\n existing_tools.add(tool)\n\n existing_config[\"allowedTools\"] = list(existing_tools)\n\n else:\n # Replace MCP config\n if \"mcpServers\" in mcp_config:\n existing_config[\"mcpServers\"] = mcp_config[\"mcpServers\"]\n\n # Write back\n with open(config_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(existing_config, f, indent=2, ensure_ascii=False)\n\n servers_count = len(existing_config.get(\"mcpServers\", {}))\n mcp_tools = len([t for t in existing_config.get(\"allowedTools\", []) if t.startswith(\"mcp__\")])\n\n return {\n \"status\": \"success\",\n \"mode\": \"merge\" if merge else \"replace\",\n \"mcp_servers\": servers_count,\n \"mcp_tools\": mcp_tools\n }\n\n def sync(\n self,\n force: bool = False,\n components: list = None\n ) -> dict:\n \"\"\"\n Execute full sync\n\n Args:\n force: Overwrite existing files\n components: List of components to sync (default: all)\n Options: [\"output_styles\", \"claude_md\", \"mcp_config\"]\n\n Returns:\n Sync results\n \"\"\"\n print(f\"[*] Claude Code Environment Sync Tool\")\n print(f\"[*] Skill Dir: {self.skill_dir}\")\n print(f\"[*] Config Dir: {self.config_dir}\")\n print(f\"[*] Claude Dir: {self.claude_dir}\")\n print(\"-\" * 50)\n\n if components is None:\n components = [\"output_styles\", \"claude_md\", \"mcp_config\"]\n\n results = {\n \"skill_dir\": str(self.skill_dir),\n \"claude_dir\": str(self.claude_dir),\n \"components\": {}\n }\n\n # 1. Sync output-styles\n if \"output_styles\" in components:\n print(\"[1/3] Syncing output-styles...\")\n results[\"components\"][\"output_styles\"] = self.sync_output_styles(force)\n if results[\"components\"][\"output_styles\"][\"total\"] > 0:\n print(f\" OK: {results['components']['output_styles']['total']} styles synced\")\n if results[\"components\"][\"output_styles\"][\"skipped\"]:\n print(f\" SKIP: {len(results['components']['output_styles']['skipped'])}\")\n else:\n print(f\" SKIP: {results['components']['output_styles'].get('reason', 'unknown')}\")\n\n # 2. Sync CLAUDE.md\n if \"claude_md\" in components:\n print(\"[2/3] Syncing CLAUDE.md...\")\n results[\"components\"][\"claude_md\"] = self.sync_claude_md(force)\n if results[\"components\"][\"claude_md\"][\"status\"] == \"success\":\n print(f\" OK: CLAUDE.md synced\")\n else:\n print(f\" SKIP: {results['components']['claude_md'].get('reason', 'unknown')}\")\n\n # 3. Sync MCP config\n if \"mcp_config\" in components:\n print(\"[3/3] Syncing MCP config...\")\n results[\"components\"][\"mcp_config\"] = self.sync_mcp_config(merge=True)\n if results[\"components\"][\"mcp_config\"][\"status\"] == \"success\":\n print(f\" OK: {results['components']['mcp_config']['mcp_servers']} servers, \"\n f\"{results['components']['mcp_config']['mcp_tools']} tools\")\n else:\n print(f\" SKIP: {results['components']['mcp_config'].get('reason', 'unknown')}\")\n\n print(\"-\" * 50)\n print(\"[OK] Sync complete!\")\n print(\"\\n[INFO] Please restart Claude Code to apply changes.\")\n\n return results\n\n\ndef main():\n import argparse\n\n parser = argparse.ArgumentParser(description=\"Claude Code Environment Sync Tool\")\n parser.add_argument(\"--skill-dir\", help=\"Skill directory (default: auto-detect)\")\n parser.add_argument(\"--claude-dir\", help=\"Claude config directory\")\n parser.add_argument(\"--force\", \"-f\", action=\"store_true\", help=\"Overwrite existing files\")\n parser.add_argument(\n \"--components\",\n nargs=\"+\",\n choices=[\"output_styles\", \"claude_md\", \"mcp_config\"],\n help=\"Components to sync (default: all)\"\n )\n\n args = parser.parse_args()\n\n syncer = ClaudeEnvSync(\n skill_dir=args.skill_dir,\n claude_dir=args.claude_dir\n )\n\n results = syncer.sync(force=args.force, components=args.components)\n\n # Save record\n from datetime import datetime\n timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n record_path = Path.home() / f\"sync_record_{timestamp}.json\"\n with open(record_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(results, f, indent=2, ensure_ascii=False)\n print(f\"\\n[LOG] Sync record: {record_path}\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9288,"content_sha256":"85b85ba39798821f2cdbaaad7742ce79d2f7fd5af2ad8f54057fef716095dfa6"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Claude Code & OpenClaw 环境一键同步工具","type":"text"}]},{"type":"paragraph","content":[{"text":"从 GitHub 仓库一键同步所有配置到本地 Claude Code 和 OpenClaw 环境。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"功能概述","type":"text"}]},{"type":"paragraph","content":[{"text":"本 skill 提供","type":"text"},{"text":"一键同步","type":"text","marks":[{"type":"strong"}]},{"text":"功能,将配置从 GitHub 仓库同步到本地:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sync_env.py","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - 同步所有配置到本地","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"同步内容","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"组件","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"来源","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"目标","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"说明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Output Styles","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"config/output-styles/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.claude/output-styles/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Claude Code 对话风格","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CLAUDE.md","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"config/CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.claude/CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"全局提示词","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MCP Config","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"config/mcp_config.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.claude.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MCP 服务器(合并)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Agent Configs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"agents/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.openclaw/agents/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OpenClaw Agent 配置","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MCP Servers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mcp/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"集成到 ","type":"text"},{"text":"~/.claude.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MCP 服务器独立配置","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Plugins","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"plugins/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.openclaw/plugins/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OpenClaw 插件配置","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"GitHub 仓库结构","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"your-claude-env/ (GitHub 仓库)\n├── env-setup.skill/ (或任意名称,放在 skills/ 下)\n│ ├── SKILL.md\n│ ├── scripts/\n│ │ ├── sync_env.py (主同步脚本)\n│ │ ├── backup_env.py (备份脚本,可选)\n│ │ └── restore_env.py (恢复脚本,可选)\n│ └── config/ (配置模板目录)\n│ ├── output-styles/ (对话风格配置)\n│ ├── CLAUDE.md (全局提示词)\n│ └── mcp_config.json (MCP服务器配置)\n├── agents/ (Agent 配置目录)\n│ ├── multimodal-agent/\n│ │ ├── AGENT.md\n│ │ └── system.md\n│ ├── healthcare-monitor/\n│ └── ...\n├── mcp/ (MCP 服务器配置)\n│ ├── github/\n│ │ ├── config.json\n│ │ └── README.md\n│ ├── lark-mcp/\n│ └── ...\n└── plugins/ (插件配置)\n ├── feishu/\n │ └── config.json\n ├── telegram/\n └── ...","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"配置目录说明","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"agents/ - Agent 配置","type":"text"}]},{"type":"paragraph","content":[{"text":"用于存放 OpenClaw Agent 的配置:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"agents/\n├── multimodal-agent/\n│ ├── AGENT.md (Agent 描述)\n│ └── system.md (System prompt)\n├── healthcare-monitor/\n│ ├── AGENT.md\n│ └── system.md\n└── ...","type":"text"}]},{"type":"paragraph","content":[{"text":"同步目标:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"~/.openclaw/agents/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"mcp/ - MCP 服务器配置","type":"text"}]},{"type":"paragraph","content":[{"text":"用于存放 MCP 服务器的独立配置:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"mcp/\n├── github/\n│ ├── config.json (MCP 服务器配置)\n│ └── README.md (使用说明)\n├── lark-mcp/\n│ └── ...\n└── ...","type":"text"}]},{"type":"paragraph","content":[{"text":"同步目标:","type":"text","marks":[{"type":"strong"}]},{"text":" 集成到 ","type":"text"},{"text":"~/.claude.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的 mcpServers","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"plugins/ - 插件配置","type":"text"}]},{"type":"paragraph","content":[{"text":"用于存放 OpenClaw 插件配置:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"plugins/\n├── feishu/\n│ └── config.json\n├── telegram/\n│ └── config.json\n└── ...","type":"text"}]},{"type":"paragraph","content":[{"text":"同步目标:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"~/.openclaw/plugins/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"使用方法","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"一、初始化 GitHub 仓库","type":"text"}]},{"type":"paragraph","content":[{"text":"在主设备上创建仓库:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. 创建项目目录\nmkdir claude-env-sync\ncd claude-env-sync\n\n# 2. 复制 env-setup skill\ncp -r ~/.claude/skills/env-setup ./\n\n# 3. 复制当前配置到 config/\ncp -r ~/.claude/output-styles/* env-setup/config/output-styles/\ncp ~/.claude/CLAUDE.md env-setup/config/\n\n# 4. 复制 agents 配置(如果有)\ncp -r ~/.openclaw/agents/* env-setup/agents/\n\n# 5. 提取 MCP 配置(如果有独立配置)\nmkdir -p env-setup/mcp\n# (手动复制 MCP 服务器配置)\n\n# 6. 提取插件配置(如果有)\nmkdir -p env-setup/plugins\n# (手动复制插件配置)\n\n# 7. 推送到 GitHub\ngit init\ngit add .\ngit commit -m \"Initial Claude env config\"\ngit remote add origin https://github.com/yourusername/claude-env-sync.git\ngit push -u origin main","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"二、在新设备上同步","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. 克隆仓库到 skills 目录\ncd ~/.claude/skills\ngit clone https://github.com/yourusername/claude-env-sync.git\n\n# 2. 运行同步脚本\npython ~/.claude/skills/claude-env-sync/env-setup/scripts/sync_env.py\n\n# 3. 重启 Claude Code / OpenClaw Gateway","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"三、命令行选项","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 基本用法(同步所有配置)\npython scripts/sync_env.py\n\n# 强制覆盖已存在的文件\npython scripts/sync_env.py --force\n\n# 只同步特定组件\npython scripts/sync_env.py --components agents mcp plugins\n\n# 指定配置目录\npython scripts/sync_env.py --claude-dir \"/path/to/.claude\" --openclaw-dir \"/path/to/.openclaw\"","type":"text"}]},{"type":"paragraph","content":[{"text":"同步选项:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"output_styles","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步对话风格配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"claude_md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步全局 CLAUDE.md","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mcp_config","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步 MCP 服务器配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"agents","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步 Agent 配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"mcp","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步独立 MCP 服务器配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"plugins","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 同步插件配置","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"配置文件格式","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"mcp_config.json","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"mcpServers\": {\n \"chrome-devtools\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"chrome-devtools-mcp@latest\"]\n },\n \"github\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@modelcontextprotocol/server-github\"],\n \"env\": {\n \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"your_token_here\"\n }\n }\n }\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"⚠️ 安全提醒:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不要在仓库中提交真实的 API keys 或 tokens!","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"敏感信息应使用环境变量或本地配置文件(在 .gitignore 中排除)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"使用占位符标注需要填写的密钥位置,例如:","type":"text"},{"text":"\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"your_token_here\"","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Agent 配置","type":"text"}]},{"type":"paragraph","content":[{"text":"每个 Agent 目录包含:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AGENT.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Agent 描述和能力说明","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"system.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - System prompt","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"插件配置","type":"text"}]},{"type":"paragraph","content":[{"text":"每个插件目录包含:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"config.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 或 ","type":"text"},{"text":"config.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" - 插件配置","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"使用场景","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"场景 1:多设备环境统一","type":"text"}]},{"type":"paragraph","content":[{"text":"在多台电脑上保持一致的配置:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 主设备:更新配置后\ngit add .\ngit commit -m \"Update config\"\ngit push\n\n# 其他设备:拉取并同步\ngit pull\npython scripts/sync_env.py","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"场景 2:团队共享配置","type":"text"}]},{"type":"paragraph","content":[{"text":"团队成员共享统一的配置:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"创建团队 GitHub 仓库","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"每个成员克隆到 ","type":"text"},{"text":"~/.claude/skills/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"定期运行 ","type":"text"},{"text":"sync_env.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 同步更新","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"场景 3:快速换电脑","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 新电脑上\ngit clone https://github.com/yourusername/claude-env-sync.git ~/.claude/skills/\npython ~/.claude/skills/claude-env-sync/env-setup/scripts/sync_env.py --force","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"场景 4:版本管理配置","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 回滚到之前的配置\ngit log --oneline\ngit checkout \u003ccommit-hash>\npython scripts/sync_env.py --force","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"工作流程","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"日常更新流程","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"1. 修改本地配置\n ↓\n2. 更新相应目录 (config/, agents/, mcp/, plugins/)\n ↓\n3. git add . && git commit -m \"Update xxx\"\n ↓\n4. git push\n ↓\n5. 其他设备: git pull && python scripts/sync_env.py","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"注意事项","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"配置同步策略","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MCP 配置","type":"text","marks":[{"type":"strong"}]},{"text":":采用合并模式,不会覆盖现有的其他 MCP 服务器","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Agent 配置","type":"text","marks":[{"type":"strong"}]},{"text":":直接复制到目标目录,会覆盖同名 Agent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"插件配置","type":"text","marks":[{"type":"strong"}]},{"text":":直接复制到目标目录,会覆盖同名插件","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不使用 ","type":"text","marks":[{"type":"strong"}]},{"text":"--force","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":":跳过已存在的文件(除了 MCP 配置,始终合并)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"使用 ","type":"text","marks":[{"type":"strong"}]},{"text":"--force","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":":覆盖已存在的文件","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"重启应用","type":"text"}]},{"type":"paragraph","content":[{"text":"同步完成后需要","type":"text"},{"text":"重启","type":"text","marks":[{"type":"strong"}]},{"text":"才能生效:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Claude Code","type":"text","marks":[{"type":"strong"}]},{"text":" - Output styles 会重新加载","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OpenClaw Gateway","type":"text","marks":[{"type":"strong"}]},{"text":" - Agents/MCP/Plugins 会重新加载","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"敏感信息管理","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不要提交真实的 API keys 或 tokens","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"使用环境变量或本地配置文件","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"在 .gitignore 中排除敏感文件","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# .gitignore 示例\nconfig/mcp_config.json.local\nconfig/secrets/\n*.key\n*.token\n.env","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"跨平台兼容","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"脚本自动处理 Windows/macOS/Linux 路径差异","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"配置文件使用 UTF-8 编码","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"高级用法","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"分支管理","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 创建设备特定配置分支\ngit checkout -b my-custom-config\n\n# 切换回主配置\ngit checkout main","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"部分同步","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 只同步 agents 和 plugins,不改变其他配置\npython scripts/sync_env.py --components agents plugins\n\n# 只同步 MCP,不改变其他配置\npython scripts/sync_env.py --components mcp","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"自动化同步(可选)","type":"text"}]},{"type":"paragraph","content":[{"text":"创建定期同步脚本:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# sync.sh\n#!/bin/bash\ncd ~/.claude/skills/claude-env-sync\ngit pull\npython env-setup/scripts/sync_env.py","type":"text"}]},{"type":"paragraph","content":[{"text":"添加到 cron 或 Task Scheduler 定期执行。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"故障排查","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"同步失败","type":"text"}]},{"type":"paragraph","content":[{"text":"问题:","type":"text","marks":[{"type":"strong"}]},{"text":" \"config/agents not found\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解决:","type":"text","marks":[{"type":"strong"}]},{"text":" 确认仓库结构正确","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"问题:","type":"text","marks":[{"type":"strong"}]},{"text":" \".claude.json not found\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解决:","type":"text","marks":[{"type":"strong"}]},{"text":" 确认 Claude Code/OpenClaw 已安装并运行过一次","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"问题:","type":"text","marks":[{"type":"strong"}]},{"text":" Agent 配置没有生效","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解决:","type":"text","marks":[{"type":"strong"}]},{"text":" 检查配置格式是否正确,重启 OpenClaw Gateway","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Git 相关","type":"text"}]},{"type":"paragraph","content":[{"text":"问题:","type":"text","marks":[{"type":"strong"}]},{"text":" 推送失败","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解决:","type":"text","marks":[{"type":"strong"}]},{"text":" 检查 GitHub 仓库权限、网络连接","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"密钥安全问题","type":"text"}]},{"type":"paragraph","content":[{"text":"问题:","type":"text","marks":[{"type":"strong"}]},{"text":" 意外提交了密钥","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解决:","type":"text","marks":[{"type":"strong"}]},{"text":" 使用 git filter-branch 或 git filter-repo 从历史中删除","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 从历史中删除包含密钥的文件\ngit filter-branch --force --tree-filter 'git rm -f filename' -- --all\n\n# 强制推送\ngit push origin --force --all","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"env-setup","author":"@skillopedia","source":{"stars":65,"repo_name":"claude-code-skills","origin_url":"https://github.com/aaaaqwq/claude-code-skills/blob/HEAD/skills/env-setup/SKILL.md","repo_owner":"aaaaqwq","body_sha256":"f613570c9608b9ab0f0ab61e3a4fe0fb450c52606320301617a73d586d4889f6","cluster_key":"b5b419776f22441fa6a440b4fee4943e7641dda4e8ad09146109a98b75444091","clean_bundle":{"format":"clean-skill-bundle-v1","source":"aaaaqwq/claude-code-skills/skills/env-setup/SKILL.md","attachments":[{"id":"f97e0cb8-a8cc-5045-b041-bd8c079f9d9b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f97e0cb8-a8cc-5045-b041-bd8c079f9d9b/attachment.md","path":"config/CLAUDE.md","size":3234,"sha256":"63a48bcaa8b304c32b55280625484637f5174f6a6423c80c08c7925cd7382595","contentType":"text/markdown; charset=utf-8"},{"id":"450e7662-c49b-5c73-8685-0b72116cb68c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/450e7662-c49b-5c73-8685-0b72116cb68c/attachment.toml","path":"config/ccline/models.toml","size":466,"sha256":"de7944e62fa7fd0deb9abd078f8417a912424d1287c55611897f7eeca2deef08","contentType":"text/plain; charset=utf-8"},{"id":"09eef361-f335-5593-96d5-4ff5500b4765","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/09eef361-f335-5593-96d5-4ff5500b4765/attachment.toml","path":"config/ccline/themes/cometix.toml","size":1896,"sha256":"963c1b741aa9f45153abf63a12f8fc2a5077f1f2c8f96c8f773682dee25c4646","contentType":"text/plain; charset=utf-8"},{"id":"5cd95516-88eb-5fe3-bd04-9ae92a4584e7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5cd95516-88eb-5fe3-bd04-9ae92a4584e7/attachment.toml","path":"config/ccline/themes/default.toml","size":1899,"sha256":"bf983c88cb733b6f50f8e4eb59138a16021aaa58a94ffeeaba27c92589269b1b","contentType":"text/plain; charset=utf-8"},{"id":"f999d16a-0ca2-51f5-b86e-d08da1e57684","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f999d16a-0ca2-51f5-b86e-d08da1e57684/attachment.toml","path":"config/ccline/themes/gruvbox.toml","size":1924,"sha256":"fc42c9f5d30471999bd9abfba3aa3eaf405f146fbc5d5943f59fd798f54784f5","contentType":"text/plain; charset=utf-8"},{"id":"0c4c6e0a-cb7e-5a0b-8c66-a1ba67031784","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0c4c6e0a-cb7e-5a0b-8c66-a1ba67031784/attachment.toml","path":"config/ccline/themes/minimal.toml","size":1895,"sha256":"fd7f18faec4d26c597133141e9069be4119ec6a51ae3a6edbb686c51ecd4c31a","contentType":"text/plain; charset=utf-8"},{"id":"46a79733-0469-5464-a290-1844cf9fedad","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/46a79733-0469-5464-a290-1844cf9fedad/attachment.toml","path":"config/ccline/themes/nord.toml","size":2449,"sha256":"a498aca79ec68483ccffcc6148a7f120067fa822facd5ee3923cf368f37b693b","contentType":"text/plain; charset=utf-8"},{"id":"fcdecbeb-e820-5269-8876-27d1a9f79dca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fcdecbeb-e820-5269-8876-27d1a9f79dca/attachment.toml","path":"config/ccline/themes/powerline-dark.toml","size":2484,"sha256":"faf75bd619479ae32f4a3c3841641d7637575ad15e54f194b43ec30c84a098aa","contentType":"text/plain; charset=utf-8"},{"id":"5861b7b3-f537-5967-9dec-df0e2cee284a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5861b7b3-f537-5967-9dec-df0e2cee284a/attachment.toml","path":"config/ccline/themes/powerline-light.toml","size":2486,"sha256":"d072e5bdff0d93ed8bc0bd10bb3096207bb062b03943f7bcc5a3ab1ffe6d1172","contentType":"text/plain; charset=utf-8"},{"id":"1127f31a-a801-5f55-8826-45be3779001e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1127f31a-a801-5f55-8826-45be3779001e/attachment.toml","path":"config/ccline/themes/powerline-rose-pine.toml","size":2487,"sha256":"0b024476cd3968cef138698368354393603dfb099826b64438e2b6d86c5758f5","contentType":"text/plain; charset=utf-8"},{"id":"ea2aa571-7086-59e8-977b-bee460d72890","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea2aa571-7086-59e8-977b-bee460d72890/attachment.toml","path":"config/ccline/themes/powerline-tokyo-night.toml","size":2494,"sha256":"3c71d812cb0f2886dfd42c37681e7dedd916422df43b64f7a98b2aa66989e78d","contentType":"text/plain; charset=utf-8"},{"id":"93a9cb3f-a5e0-5a19-a05f-f7fb58fa7780","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/93a9cb3f-a5e0-5a19-a05f-f7fb58fa7780/attachment.json","path":"config/mcp_config.json","size":3720,"sha256":"932004db09786693b74e001198bd4f0c4781e740e8d4815bb13bf91c13bf2ae2","contentType":"application/json; charset=utf-8"},{"id":"a95470fb-dab0-56b1-8343-5fcf53794a12","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a95470fb-dab0-56b1-8343-5fcf53794a12/attachment.md","path":"config/output-styles/engineer-professional.md","size":2835,"sha256":"8873b456a7bd61305c070bc3f26aa0b3ab3ff316a1e58718f6a49f35ffc22624","contentType":"text/markdown; charset=utf-8"},{"id":"b3be4f74-b558-5597-b8a7-5058fbc825cb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b3be4f74-b558-5597-b8a7-5058fbc825cb/attachment.md","path":"config/output-styles/laowang-engineer.md","size":6573,"sha256":"d04aa4a8faa39cf5f77330c55d48c5604e57ffb2883b4f86f4ab3ef4507e5418","contentType":"text/markdown; charset=utf-8"},{"id":"c819a773-1bb2-510c-87c0-008958ced339","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c819a773-1bb2-510c-87c0-008958ced339/attachment.md","path":"config/output-styles/nekomata-engineer.md","size":5343,"sha256":"3d54195b8d0cd0a55c74eb4be8d97c3912517290875092cc918dfdc4b0541571","contentType":"text/markdown; charset=utf-8"},{"id":"182f28e2-e7f7-5ae9-86eb-7e3460cf4a21","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/182f28e2-e7f7-5ae9-86eb-7e3460cf4a21/attachment.md","path":"config/output-styles/ojousama-engineer.md","size":6810,"sha256":"361094e406f97b00f6f1eb8deb24d4425dfc99e27fa6b37ebbf4439ef6d0adba","contentType":"text/markdown; charset=utf-8"},{"id":"2f0c46f0-2425-584d-a7df-1caf4f7b0081","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2f0c46f0-2425-584d-a7df-1caf4f7b0081/attachment.md","path":"config/servers/CHANGELOG.md","size":38137,"sha256":"db577ea1f01211ba1c239afe912dc9bd4d440c9f626639a034f840dd15804c16","contentType":"text/markdown; charset=utf-8"},{"id":"32334d93-7a70-5678-84e2-40a4ca860ba2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32334d93-7a70-5678-84e2-40a4ca860ba2/attachment.md","path":"config/servers/CONTRIBUTING.md","size":2579,"sha256":"0711d4387ceb092855fb09bd4cdf045883099a4d28cf845940ef175dc998c603","contentType":"text/markdown; charset=utf-8"},{"id":"bd6a69b4-c1d3-56e5-be81-181f3fbb8e19","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bd6a69b4-c1d3-56e5-be81-181f3fbb8e19/attachment.md","path":"config/servers/README.md","size":23630,"sha256":"cf8b906009a2a009069875d8a1ee6e55967f69f546aa35d238e755aeaeb1dec6","contentType":"text/markdown; charset=utf-8"},{"id":"82b46746-d93a-5842-910d-afb774f7063d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/82b46746-d93a-5842-910d-afb774f7063d/attachment.md","path":"config/servers/SECURITY.md","size":218,"sha256":"a9267bf61bac6ed5aa44c0e54316051afcc8bb75025ead91252ba65e22931841","contentType":"text/markdown; charset=utf-8"},{"id":"3c763aab-9e65-57b1-b0e0-83988b86766a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3c763aab-9e65-57b1-b0e0-83988b86766a/attachment.js","path":"config/servers/build/src/DevToolsConnectionAdapter.js","size":2596,"sha256":"b062473729a18db107e67ff8ae6717dd3beb47d8fbc571bc1f6720968b43f909","contentType":"application/javascript; charset=utf-8"},{"id":"8f81e05d-ea02-5660-85dd-776111057aa4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8f81e05d-ea02-5660-85dd-776111057aa4/attachment.js","path":"config/servers/build/src/DevtoolsUtils.js","size":7212,"sha256":"f5d27937943afebca389a0abf6b859cb72b81028d9d95c158111229fa8c4ac35","contentType":"application/javascript; charset=utf-8"},{"id":"106dbbf6-f46b-5c9c-8091-1589a71f95de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/106dbbf6-f46b-5c9c-8091-1589a71f95de/attachment.js","path":"config/servers/build/src/McpContext.js","size":18375,"sha256":"025d6e442ff1ec4fc9199193c795ea6d41a8b1fb0ea0daece17ea0f86d6d6d9c","contentType":"application/javascript; charset=utf-8"},{"id":"dcc581d1-3e96-546d-8adc-f6f9bdc44998","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dcc581d1-3e96-546d-8adc-f6f9bdc44998/attachment.js","path":"config/servers/build/src/McpResponse.js","size":15817,"sha256":"807dc23d262707c2d3cac72ab8ba9a7b423833fba739b2900e17e8dc314d8190","contentType":"application/javascript; charset=utf-8"},{"id":"018241c1-f3fa-5ad7-9330-db3582af7960","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/018241c1-f3fa-5ad7-9330-db3582af7960/attachment.js","path":"config/servers/build/src/Mutex.js","size":857,"sha256":"0842a55c0503f5944d7596115ee0bd1800d5f1b867e473cfc66b091429c885cf","contentType":"application/javascript; charset=utf-8"},{"id":"ef76f3a6-e9a4-5460-b718-225b3c092b8a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ef76f3a6-e9a4-5460-b718-225b3c092b8a/attachment.js","path":"config/servers/build/src/PageCollector.js","size":9782,"sha256":"6c103a482d3964e031009d59ee97920e95254d07ffe23469a8b3fa80d8e4bd81","contentType":"application/javascript; charset=utf-8"},{"id":"92c1f0b4-011c-529e-a171-fd14c7aa14ee","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/92c1f0b4-011c-529e-a171-fd14c7aa14ee/attachment.js","path":"config/servers/build/src/WaitForHelper.js","size":4820,"sha256":"0b21a33c6639dbef6b61554203222c9a4edf5c978fa2fc1b8bcf9ad75a32cc0e","contentType":"application/javascript; charset=utf-8"},{"id":"ba9b3d95-5f3b-5827-8268-5209f6dd7d16","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ba9b3d95-5f3b-5827-8268-5209f6dd7d16/attachment.js","path":"config/servers/build/src/browser.js","size":6235,"sha256":"5e2326314c331f2c8e97d1b69f86313d51cb52d37ff181a8837d2c194a013637","contentType":"application/javascript; charset=utf-8"},{"id":"f89c7998-33ba-5a8c-96fd-316e9e27bd86","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f89c7998-33ba-5a8c-96fd-316e9e27bd86/attachment.js","path":"config/servers/build/src/cli.js","size":8913,"sha256":"ad41e5242d6ad16ac5a7a2c06a3a2c70f35ad19b4136f73ec7d647c38a85422c","contentType":"application/javascript; charset=utf-8"},{"id":"7a81d93a-eba7-59a5-bd0d-f9fdcd70b410","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7a81d93a-eba7-59a5-bd0d-f9fdcd70b410/attachment.js","path":"config/servers/build/src/formatters/consoleFormatter.js","size":4513,"sha256":"df2f04a07cebd1dcf9fe04c5a5fc797685d346be4bea0a7eabf6d3e8f1d79f38","contentType":"application/javascript; charset=utf-8"},{"id":"a638747d-d648-5aa6-b3fe-45fce599ba4c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a638747d-d648-5aa6-b3fe-45fce599ba4c/attachment.js","path":"config/servers/build/src/formatters/networkFormatter.js","size":2541,"sha256":"9987a3f9fdbf69b1bff438130722fd36ec6b309dd468708da46ec2dbab33e85f","contentType":"application/javascript; charset=utf-8"},{"id":"b0d07a4b-2848-53bf-8da4-204e215d94da","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b0d07a4b-2848-53bf-8da4-204e215d94da/attachment.js","path":"config/servers/build/src/formatters/snapshotFormatter.js","size":2405,"sha256":"16ff24016780c582adf66391f29d20e081bacc5d3442d944dd51273a4496df8c","contentType":"application/javascript; charset=utf-8"},{"id":"376b4695-55c9-528f-acda-7dce6b4ad052","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/376b4695-55c9-528f-acda-7dce6b4ad052/attachment.js","path":"config/servers/build/src/index.js","size":836,"sha256":"fe90e94dfe6871fd0b39331941abd4e6274a5f34485d71181382fb46c36876d4","contentType":"application/javascript; charset=utf-8"},{"id":"221f20bb-e684-5f63-a616-c7333d9c4259","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/221f20bb-e684-5f63-a616-c7333d9c4259/attachment.js","path":"config/servers/build/src/issue-descriptions.js","size":1220,"sha256":"a3c67d796363ff056fa2191088012dd2d08450b0986c790e525f47328934276f","contentType":"application/javascript; charset=utf-8"},{"id":"2248514c-615b-5194-ae9c-d30bd1dc72c4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2248514c-615b-5194-ae9c-d30bd1dc72c4/attachment.js","path":"config/servers/build/src/logger.js","size":879,"sha256":"b685f545984d26a00ce7ea05534033d4e22ecf5de9e8a2f0fe7ab201314685ee","contentType":"application/javascript; charset=utf-8"},{"id":"0af8ece4-3c20-5cc2-ba96-b038187856e5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0af8ece4-3c20-5cc2-ba96-b038187856e5/attachment.js","path":"config/servers/build/src/main.js","size":5131,"sha256":"0ea0976db2af660c36ea13121a2121b8d28ca6dd299866ac751cdc20b23eb573","contentType":"application/javascript; charset=utf-8"},{"id":"89da0098-f596-52b8-bc6c-b0c7664561d1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/89da0098-f596-52b8-bc6c-b0c7664561d1/attachment.js","path":"config/servers/build/src/polyfill.js","size":179,"sha256":"40d227982c39bf409459301a917a3014f08ac723c0c300fedd33a3c96e0cc657","contentType":"application/javascript; charset=utf-8"},{"id":"c3191dbc-d9e5-5f43-9204-0f30e3a813a8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c3191dbc-d9e5-5f43-9204-0f30e3a813a8/attachment.js","path":"config/servers/build/src/third_party/devtools.js","size":442,"sha256":"dbb5762be81236345e2244d2f90554b666e629f8cdc1fcc79337b3c4cc446e1f","contentType":"application/javascript; charset=utf-8"},{"id":"ce6f715f-b13c-5718-ad94-6bbb5ab92d22","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ce6f715f-b13c-5718-ad94-6bbb5ab92d22/attachment.js","path":"config/servers/build/src/third_party/index.js","size":876,"sha256":"724d4cb054108e647f34fb9d19947ac8722440b4d9a20ebea2390c35d2bd5eb4","contentType":"application/javascript; charset=utf-8"},{"id":"278943a2-fb4f-55bf-ac24-5dd13cbb2cd6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/278943a2-fb4f-55bf-ac24-5dd13cbb2cd6/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CoepCoopSandboxedIframeCannotNavigateToCoopPage.md","size":306,"sha256":"3582188fd1f2d2d72d5cc1e2d465077d124945c1199ec63194618ba22c0dc1db","contentType":"text/markdown; charset=utf-8"},{"id":"95ff300e-e325-50aa-8be0-a0972fb1a8d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/95ff300e-e325-50aa-8be0-a0972fb1a8d3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md","size":677,"sha256":"a38f4cefff1cca11ba3c1bcc8e97f77bee173ba471c8bbd44845c4e44434eff5","contentType":"text/markdown; charset=utf-8"},{"id":"e4dc230e-e8fb-5878-8154-4eb1b8364a0e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e4dc230e-e8fb-5878-8154-4eb1b8364a0e/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md","size":969,"sha256":"57d1fd495ce18553066b83430ff1773ec46dc156ea32ccca98f8a8ff845bd22a","contentType":"text/markdown; charset=utf-8"},{"id":"b103a61b-1daa-529d-87d0-4f8c133e8acd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b103a61b-1daa-529d-87d0-4f8c133e8acd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md","size":499,"sha256":"9a3c183154f17ac8cf9fb5bd25b8e463d5dbf0d14a2e27d5504fd264417a0e4a","contentType":"text/markdown; charset=utf-8"},{"id":"e560e43d-b9ac-523c-a0a4-bfcc7247f1c4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e560e43d-b9ac-523c-a0a4-bfcc7247f1c4/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md","size":488,"sha256":"687bad145b9bc77ad536d0d4e21cd45fa1df5cf2f06b5725691f882b8d672b81","contentType":"text/markdown; charset=utf-8"},{"id":"f09438c6-013f-52fd-a700-6f2ddb345260","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f09438c6-013f-52fd-a700-6f2ddb345260/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md","size":424,"sha256":"bd7a675949afa199fcbfec84ef27c24e4ed046c006a3ee5bdb4baa189656a233","contentType":"text/markdown; charset=utf-8"},{"id":"2c4677c4-6caf-5aa5-9ada-d5ecdf51b873","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2c4677c4-6caf-5aa5-9ada-d5ecdf51b873/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md","size":404,"sha256":"3c4bbcee9a8e79bd654ec889d9febd234893e10e4ea959868488098384cb40fd","contentType":"text/markdown; charset=utf-8"},{"id":"1e4d0cd3-5e89-5cd2-af2f-bec179d6ae26","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1e4d0cd3-5e89-5cd2-af2f-bec179d6ae26/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/LowTextContrast.md","size":775,"sha256":"92a090c655d3a1de206edcc808ed46415234e654528bcb4e75037c3e6722feb6","contentType":"text/markdown; charset=utf-8"},{"id":"cdbfd658-93b3-52e6-a67f-acde54d97b58","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cdbfd658-93b3-52e6-a67f-acde54d97b58/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md","size":571,"sha256":"c66bf7bb54b1962aa39004d5f56bc73099900fd74145aedbf838bafe7ce2a8de","contentType":"text/markdown; charset=utf-8"},{"id":"941eb0ce-43af-5afd-b32a-f810def86c7d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/941eb0ce-43af-5afd-b32a-f810def86c7d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md","size":572,"sha256":"680f8c63315273d54c571087701f98a2f3755713a52d88d6c4ebed25f8a8c99e","contentType":"text/markdown; charset=utf-8"},{"id":"3a2b7187-5562-5247-8d44-189f2729b26c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3a2b7187-5562-5247-8d44-189f2729b26c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md","size":583,"sha256":"0e4129cd3b2b4c31de9ab5cef2530639cc2c8156c881252f5603e715694ad3d5","contentType":"text/markdown; charset=utf-8"},{"id":"87eb29f3-cd20-590f-b7a1-42654984c644","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/87eb29f3-cd20-590f-b7a1-42654984c644/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteInvalidSameParty.md","size":525,"sha256":"0c78bf06d8dcfd5c8346cc439ba2717170983603511d75ac2b7ee4ed413f8f9a","contentType":"text/markdown; charset=utf-8"},{"id":"122145eb-f653-5180-a238-fb89dcafcb6d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/122145eb-f653-5180-a238-fb89dcafcb6d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md","size":556,"sha256":"0b8eb2006b387e473fb77f1712e8cf12438fb793f16eaf7b7630486000f22bda","contentType":"text/markdown; charset=utf-8"},{"id":"e426dacf-a77d-5f1c-af4d-7e9a9042d8fc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e426dacf-a77d-5f1c-af4d-7e9a9042d8fc/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md","size":611,"sha256":"00abb8f2fc2e9f75238234c7e32f6b3140f5429ba1a29eb5f8b960afa6d88b9d","contentType":"text/markdown; charset=utf-8"},{"id":"819bb484-ee12-53ac-89b6-bc5bd939c7a1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/819bb484-ee12-53ac-89b6-bc5bd939c7a1/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md","size":592,"sha256":"896df129b47b839decc155012d90ce734a3f64522e78e19cc028fb59af39bd8a","contentType":"text/markdown; charset=utf-8"},{"id":"cf064cb7-79e1-5c43-b92d-c814a14c4ecb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf064cb7-79e1-5c43-b92d-c814a14c4ecb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md","size":647,"sha256":"ebf5f29c18feda9e768f86b740c955a5289b83c74fe2b15e15f550bc0aee6c58","contentType":"text/markdown; charset=utf-8"},{"id":"2ed936e1-b0d5-5894-b0ac-4f8ce47341f9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2ed936e1-b0d5-5894-b0ac-4f8ce47341f9/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteSamePartyCrossPartyContextSet.md","size":589,"sha256":"d85353b0af80531937ca409a5ca8a7625ab2547faeaff814bca195e0f9158b00","contentType":"text/markdown; charset=utf-8"},{"id":"cb4853b1-5e17-53bc-8fc3-a80b3571f35d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb4853b1-5e17-53bc-8fc3-a80b3571f35d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md","size":699,"sha256":"20a1ab79ad5588fa7184832c97013dc12a3b3eeaf4b8526095b978db8c2b6514","contentType":"text/markdown; charset=utf-8"},{"id":"d62532c7-78a9-5b66-ac6d-e44bbd4ba335","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d62532c7-78a9-5b66-ac6d-e44bbd4ba335/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md","size":758,"sha256":"863bc6973d521147dd22268e2c9e1b2332938bf3a48b5771bb98d64653e3d92e","contentType":"text/markdown; charset=utf-8"},{"id":"c5da40d5-24e0-5f2f-b953-7e229e8e6a2d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c5da40d5-24e0-5f2f-b953-7e229e8e6a2d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md","size":610,"sha256":"08b038ca7335acc0f25c449186395f11cf034867ae4b7988ac723dbbb7140c8c","contentType":"text/markdown; charset=utf-8"},{"id":"f27d4af1-bb27-5ba7-a38f-0cde7f3a3307","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f27d4af1-bb27-5ba7-a38f-0cde7f3a3307/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md","size":612,"sha256":"bdd6e3ee0d16c02dc9cfc72e60ea25a1e7917f51d90bc25be4d258dc56c4337b","contentType":"text/markdown; charset=utf-8"},{"id":"d6e5f6d6-22d1-5efd-9141-0fe38dc4e630","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d6e5f6d6-22d1-5efd-9141-0fe38dc4e630/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md","size":622,"sha256":"141d06a5bfa4a02fc4a90f688f2b81eba48f0019b2e8e0eb09d7b195087710b1","contentType":"text/markdown; charset=utf-8"},{"id":"397b9c46-02ef-5126-bcb9-f775d6b594ef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/397b9c46-02ef-5126-bcb9-f775d6b594ef/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInsecureContext.md","size":304,"sha256":"163921e40303f5ded19de08f585ddd02d0b0bbf946d5263508343092e653bdfa","contentType":"text/markdown; charset=utf-8"},{"id":"49aeaa06-3386-5150-9964-7cd0a503e808","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/49aeaa06-3386-5150-9964-7cd0a503e808/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md","size":228,"sha256":"4b98e1ffa358588c8bf625bb9bfe0b00ca0b86b67d5fab257cbacaae4cb07049","contentType":"text/markdown; charset=utf-8"},{"id":"cf12d45a-8a20-53da-bbb2-e62aa273f88b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf12d45a-8a20-53da-bbb2-e62aa273f88b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md","size":249,"sha256":"b7021f9d81385559222e0985b79c70dd91e9c6ca0ba8ebcc16b585116e56a8c2","contentType":"text/markdown; charset=utf-8"},{"id":"802c2a36-a2fa-5994-b3b2-646b445cfe46","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/802c2a36-a2fa-5994-b3b2-646b445cfe46/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md","size":252,"sha256":"ce87557ebb39d64003ad9302f4f1444856b9c3065ffd98339a2699a9307ee5c6","contentType":"text/markdown; charset=utf-8"},{"id":"a6ea11ea-9ff4-5629-97ab-3ebe4ac060d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a6ea11ea-9ff4-5629-97ab-3ebe4ac060d0/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md","size":239,"sha256":"26d3d865a143ffb2b23b8aaa6bdc5218e040addb7ff8c5a78089daf3dccce5ed","contentType":"text/markdown; charset=utf-8"},{"id":"e68dfb4f-7661-5e66-b7d6-408ccaaf7381","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e68dfb4f-7661-5e66-b7d6-408ccaaf7381/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md","size":242,"sha256":"2727c20ab323cb5db230a72f7dbcb2a0a6d13524cb8b9bfbe2df79d875e26a6d","contentType":"text/markdown; charset=utf-8"},{"id":"6b549fee-27db-5d12-a279-381948a8f1d8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6b549fee-27db-5d12-a279-381948a8f1d8/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md","size":325,"sha256":"aec75f8ef2ee35a654052a7bbd8770cab9d8fa3e9fc43d5a1378cf3351510406","contentType":"text/markdown; charset=utf-8"},{"id":"3989f108-2f54-5b1d-8895-df0ae18c69d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3989f108-2f54-5b1d-8895-df0ae18c69d3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md","size":383,"sha256":"e1396ad8a2ccd10c1bedf3a05de6ac5fad2e6371b087f685aa229d8d9098f7f5","contentType":"text/markdown; charset=utf-8"},{"id":"a1de1c25-c0b5-5dd6-af27-fe7eb173ab72","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a1de1c25-c0b5-5dd6-af27-fe7eb173ab72/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md","size":298,"sha256":"2b2172144b9eb6bdc5a1d744112e4337e8b0079414f80caa7d67dfa438bf6254","contentType":"text/markdown; charset=utf-8"},{"id":"92700f2e-6b48-501a-8ee7-efce5ce9a0eb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/92700f2e-6b48-501a-8ee7-efce5ce9a0eb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md","size":301,"sha256":"4e037d4c2835caa9ea7e3d398dbb0fb395fe0de8fcfe45ac4d64778a8b35352e","contentType":"text/markdown; charset=utf-8"},{"id":"821f5a3d-ba0f-5c61-9ebd-792601a8aa96","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/821f5a3d-ba0f-5c61-9ebd-792601a8aa96/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md","size":296,"sha256":"29926fada4ae30c5b0cf5337eaddb8cc445781c0ba9af5762faf255ea5800388","contentType":"text/markdown; charset=utf-8"},{"id":"8c0aba5f-4eca-5345-9389-12e096e2083a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8c0aba5f-4eca-5345-9389-12e096e2083a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md","size":299,"sha256":"602d40960c38e355e2c1840fc09cb35a7a5431873d786f0a6792074a62fa9685","contentType":"text/markdown; charset=utf-8"},{"id":"5b5011c1-7f76-557e-8bf9-a59768963fb0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5b5011c1-7f76-557e-8bf9-a59768963fb0/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md","size":204,"sha256":"ce2fb8f31ede03e89403559b79b985cbd992033414c8ecdcbe72af0447e8e461","contentType":"text/markdown; charset=utf-8"},{"id":"474dc4d4-9dc7-513a-ba1f-831384b825bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/474dc4d4-9dc7-513a-ba1f-831384b825bd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arOsSourceIgnored.md","size":801,"sha256":"1f14d38187687d95599992987fe6e2e94ad42389ccb21edefe43f5de0b1dfc81","contentType":"text/markdown; charset=utf-8"},{"id":"c7f3a906-90cb-545a-ac86-bbeacdb7e98a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c7f3a906-90cb-545a-ac86-bbeacdb7e98a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md","size":826,"sha256":"29259dff834d43b060ea39a18a92554938b518d048c1d13341df2a6df6c1e7fc","contentType":"text/markdown; charset=utf-8"},{"id":"d54770f7-414d-5e17-929f-e909d45000bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d54770f7-414d-5e17-929f-e909d45000bd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md","size":472,"sha256":"5f1c4db582faca2577b7b52da8cce8c04bda7ba42f75adf74c81b43a33542792","contentType":"text/markdown; charset=utf-8"},{"id":"a6ae366e-08c5-5b51-81f0-7893a85d67bb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a6ae366e-08c5-5b51-81f0-7893a85d67bb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md","size":451,"sha256":"86f8ab2c1e1bf7cbace2d4f676494ef1d82a8c589634c4a8821c679da09c959e","contentType":"text/markdown; charset=utf-8"},{"id":"68457854-a0a3-5d50-a2f7-7328094e9dc7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/68457854-a0a3-5d50-a2f7-7328094e9dc7/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arSourceIgnored.md","size":698,"sha256":"c35e05690a89f3db198bfdd619563a546163191ed4306b32fd6abe30f448e596","contentType":"text/markdown; charset=utf-8"},{"id":"c4b219b6-6278-5c4b-ac02-0a89f4a25791","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c4b219b6-6278-5c4b-ac02-0a89f4a25791/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arTriggerIgnored.md","size":651,"sha256":"8dae602b85600137dc30b676fa59b38f452bff37b1ae369f98e43c956df9c53e","contentType":"text/markdown; charset=utf-8"},{"id":"4b75605e-38ea-5ded-a624-323fd57813b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4b75605e-38ea-5ded-a624-323fd57813b6/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md","size":448,"sha256":"bdd107d1927727b003c548f6bf79e7913013d987fadfbde51d5b963a00744cb3","contentType":"text/markdown; charset=utf-8"},{"id":"027268b8-e8bd-5bdf-8c90-1539854cae03","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/027268b8-e8bd-5bdf-8c90-1539854cae03/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md","size":429,"sha256":"3c29baaa40fa7abeb6134ac2bcefaba02b4cc3e45e80d742f3bb11c6303376f1","contentType":"text/markdown; charset=utf-8"},{"id":"36551c1a-c3f1-5924-9eb6-15877d84fc9d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/36551c1a-c3f1-5924-9eb6-15877d84fc9d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md","size":269,"sha256":"9b294a050bd23f64f805f2eb531a7cf8f1312b092e7dad2d391ed5c631098580","contentType":"text/markdown; charset=utf-8"},{"id":"23321027-cb37-5c4b-a7de-257977a12fa8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/23321027-cb37-5c4b-a7de-257977a12fa8/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/clientHintMetaTagAllowListInvalidOrigin.md","size":175,"sha256":"af37cca14ef910e166f689de6482edb6b6f67f380fba293d14ebfbc1af3e82bb","contentType":"text/markdown; charset=utf-8"},{"id":"9845a629-9d26-58e5-bd1b-9e09f5d382ac","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9845a629-9d26-58e5-bd1b-9e09f5d382ac/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/clientHintMetaTagModifiedHTML.md","size":189,"sha256":"1bdcc86b6bf9426c353d5b0c57c2f616ffb4eb729114f53ea3e95d166cafe778","contentType":"text/markdown; charset=utf-8"},{"id":"7f5b53b2-430e-5fa7-bff3-de641565195b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7f5b53b2-430e-5fa7-bff3-de641565195b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md","size":515,"sha256":"f4e09fa9c50ee5d760af8f2f32d897e8e8cc4bc79ae4f1e411a8ac2c2b9e728b","contentType":"text/markdown; charset=utf-8"},{"id":"dc508463-db4d-5915-922e-5e45f715db73","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dc508463-db4d-5915-922e-5e45f715db73/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md","size":182,"sha256":"90953f12dbf0946f16ff1256f8e87829f13ff47e5f750b72e7c5f39847b3a050","contentType":"text/markdown; charset=utf-8"},{"id":"88795c79-91e3-5ee8-a2e2-f19d927ccd97","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/88795c79-91e3-5ee8-a2e2-f19d927ccd97/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludeDomainNonAscii.md","size":530,"sha256":"f3e945226678843bfd0bd9d75d1ab9a50529798ba117e9c141e169160850693b","contentType":"text/markdown; charset=utf-8"},{"id":"b80ffd7d-801a-5974-8b1e-293e65ad5a07","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b80ffd7d-801a-5974-8b1e-293e65ad5a07/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md","size":387,"sha256":"84b3394b0da9de267fca7d5761144748db749e21b0f61637a73aa1c8077849ad","contentType":"text/markdown; charset=utf-8"},{"id":"670543ec-5d04-5e92-8f99-cbfe8a441718","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/670543ec-5d04-5e92-8f99-cbfe8a441718/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md","size":279,"sha256":"e137dd9850f6e72839d883bb739ec6fb7045c20a3e76ecf15821d87d59f2a1bf","contentType":"text/markdown; charset=utf-8"},{"id":"3d20a636-75d5-5ff7-b68c-9aae6b5bf367","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3d20a636-75d5-5ff7-b68c-9aae6b5bf367/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutRead.md","size":388,"sha256":"34f45001a4a5a3a2b83c9d271dd72174171bae1bd86445f2d660ca483bdb2e1b","contentType":"text/markdown; charset=utf-8"},{"id":"f1d6c483-21d5-5787-b0d2-aec34c6ab9d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f1d6c483-21d5-5787-b0d2-aec34c6ab9d3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md","size":387,"sha256":"7214f0dad983a529dfe82e69289a0938cb579e794bdc4d7e72a593b4a860deb5","contentType":"text/markdown; charset=utf-8"},{"id":"1a908895-e7fa-5b83-aa63-81ed8ad1f052","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1a908895-e7fa-5b83-aa63-81ed8ad1f052/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieWarnDomainNonAscii.md","size":544,"sha256":"947dd6d5e7daf17f433b1fcaa2dfcdbe96304cccc2f5adaca19365acdd57bdd5","contentType":"text/markdown; charset=utf-8"},{"id":"8a4c2d1d-1174-554c-80d9-c1d11adebaff","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8a4c2d1d-1174-554c-80d9-c1d11adebaff/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantRead.md","size":378,"sha256":"3f437c58f9d7019488ea67c5f4cfc355e38cd3bf1adffcb53470f8df2959529d","contentType":"text/markdown; charset=utf-8"},{"id":"dba15874-2fc8-5707-92ff-80fa962b0dcc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dba15874-2fc8-5707-92ff-80fa962b0dcc/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieWarnMetadataGrantSet.md","size":375,"sha256":"2940e4345bad4a862983a9984bd417073b588c68ebbf16221315959c5a010d41","contentType":"text/markdown; charset=utf-8"},{"id":"9d60a511-dda9-5f63-a6ba-e9adc44dedea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9d60a511-dda9-5f63-a6ba-e9adc44dedea/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md","size":400,"sha256":"6e0fbabab03f52d4979b4851723113bef0da6bc3bf6e16f0232b88f297c9c694","contentType":"text/markdown; charset=utf-8"},{"id":"f72d2b88-69bc-5f66-b2a2-95204f7490b1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f72d2b88-69bc-5f66-b2a2-95204f7490b1/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutSet.md","size":400,"sha256":"303db16c18700334eaeaa8f0a994908e392c2527d3286ee5005d5081491b8a6c","contentType":"text/markdown; charset=utf-8"},{"id":"5796c7c6-2f05-5ef1-868f-5cdf8b830d9c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5796c7c6-2f05-5ef1-868f-5cdf8b830d9c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md","size":536,"sha256":"97429f1525b2331f516af28c3bd0b61a2a79db087f26d73105de56c85ed71155","contentType":"text/markdown; charset=utf-8"},{"id":"554f6650-5dd2-5a2e-ae65-0eca2788ed6d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/554f6650-5dd2-5a2e-ae65-0eca2788ed6d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsDisabledScheme.md","size":537,"sha256":"c9e2e0e2c634c2957c0e5dde66b475dc73961cab62664de8295c21a3f10be04a","contentType":"text/markdown; charset=utf-8"},{"id":"28d8cb8f-dd80-5743-b6a6-e110f50f3171","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/28d8cb8f-dd80-5743-b6a6-e110f50f3171/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsDisallowedByMode.md","size":628,"sha256":"8882652f0bdf1e3b92f320c8ab262bdaa0fbd8a54188cc5bad26eeb4cecc5b9a","contentType":"text/markdown; charset=utf-8"},{"id":"466d295f-36d1-529a-82fa-e5eab42be474","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/466d295f-36d1-529a-82fa-e5eab42be474/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md","size":518,"sha256":"a1eed2d326cafcd0e7d62684ef974bd66cba7d5177ea11a03c2cf33e0cd68303","contentType":"text/markdown; charset=utf-8"},{"id":"4dfb7bcb-912c-50ff-b8a0-e761b056957c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4dfb7bcb-912c-50ff-b8a0-e761b056957c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsInsecurePrivateNetwork.md","size":944,"sha256":"995cbf894e4f9512cec57457158e7a88259d7f6778c6097a069031889a42797f","contentType":"text/markdown; charset=utf-8"},{"id":"18413206-5c9f-5665-a3c6-c7fa187c6bbb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/18413206-5c9f-5665-a3c6-c7fa187c6bbb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md","size":640,"sha256":"ca2b31ea960e4f48f85416b6a035afaea61a250ea18f5bf606e832e4844ed19b","contentType":"text/markdown; charset=utf-8"},{"id":"26bd06ef-72cd-5640-8f0d-429cbe70b777","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/26bd06ef-72cd-5640-8f0d-429cbe70b777/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md","size":1085,"sha256":"00571ec07648b29c9144ddd37c2633ee2fd3229beac70e45cf7c1c7ec681463c","contentType":"text/markdown; charset=utf-8"},{"id":"830359fa-d319-549f-9ace-866de1fba0c5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/830359fa-d319-549f-9ace-866de1fba0c5/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md","size":480,"sha256":"84022e2bdd585398eb8fc61e86d8730efdd5b8a692721d213823fe64dcc23ece","contentType":"text/markdown; charset=utf-8"},{"id":"475701ac-be8b-53c6-a335-b9a6ba8a443f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/475701ac-be8b-53c6-a335-b9a6ba8a443f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md","size":335,"sha256":"39c4314c49585947f6e046dd3a779a8d8ba7eb8b973760201c40d6352ae8ba7f","contentType":"text/markdown; charset=utf-8"},{"id":"47b537da-09c6-5a3b-8810-0de753577ef9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/47b537da-09c6-5a3b-8810-0de753577ef9/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsOriginMismatch.md","size":645,"sha256":"ab5d7c867407d2ad153cc93ddfa89a7c363ad2ecab477050b730d5fc763bf98a","contentType":"text/markdown; charset=utf-8"},{"id":"84d592cb-5bfc-59f7-a82d-5f6943cbeaee","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/84d592cb-5bfc-59f7-a82d-5f6943cbeaee/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsPreflightAllowPrivateNetworkError.md","size":945,"sha256":"25d5f7fee0d351db9a51fd4d59cbd1141588c9e2534c5250cd1a7c8954a159a2","contentType":"text/markdown; charset=utf-8"},{"id":"101e1904-d3a7-51ba-882c-3b82dcdcc7fb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/101e1904-d3a7-51ba-882c-3b82dcdcc7fb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md","size":394,"sha256":"d2bca84824cff8b671e74984ef3fc4f9be38511575af98c62f4630658005f72c","contentType":"text/markdown; charset=utf-8"},{"id":"e722a655-86d8-5c2c-935e-57f2bd22b8bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e722a655-86d8-5c2c-935e-57f2bd22b8bd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsPrivateNetworkPermissionDenied.md","size":1240,"sha256":"33ca74014722c5fd8dc73382b8e36527fb095c00058f78f94922a38fec8c49bb","contentType":"text/markdown; charset=utf-8"},{"id":"5229aefc-2407-589d-a4f5-f23468b9c2dc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5229aefc-2407-589d-a4f5-f23468b9c2dc/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md","size":377,"sha256":"0099164610ffb6ccf2001d3d5f1c94bbe7bad671c5623c50e001c9b07e3fba3b","contentType":"text/markdown; charset=utf-8"},{"id":"660b4931-ee12-5602-a955-a81c07c17764","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/660b4931-ee12-5602-a955-a81c07c17764/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md","size":914,"sha256":"0d86d469e51839320eb6ea2ebe01a3b234bb0233cbe44f53db218905e5ff7e7e","contentType":"text/markdown; charset=utf-8"},{"id":"ec34150d-5664-5410-8e5b-2cd903011c0f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ec34150d-5664-5410-8e5b-2cd903011c0f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cspEvalViolation.md","size":615,"sha256":"d124e984ba76a5850f2263528f29c1553e986095065a1321b79ae8e4fe73b463","contentType":"text/markdown; charset=utf-8"},{"id":"7826f397-6075-5d58-b8a7-0dc3d27fc6b0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7826f397-6075-5d58-b8a7-0dc3d27fc6b0/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cspInlineViolation.md","size":608,"sha256":"b91510f43d8fe1debf6f80c4f5de3d7f8685bb53deb6702979f19a817b983a35","contentType":"text/markdown; charset=utf-8"},{"id":"6bc87fce-6a93-543b-a7de-518a29d8757b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6bc87fce-6a93-543b-a7de-518a29d8757b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md","size":616,"sha256":"04196592f97f698ea444dc2bcad7683f64bc552109c2631645934f5a0912f9ec","contentType":"text/markdown; charset=utf-8"},{"id":"17c70edc-946e-5e10-aedf-b46ef0b5751f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/17c70edc-946e-5e10-aedf-b46ef0b5751f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md","size":544,"sha256":"2537ac5b992e551cd871d30d5d3b6742033df0c22cb89573182f6bf5001b969d","contentType":"text/markdown; charset=utf-8"},{"id":"3700e39d-8cbf-5ad5-999c-73eff25385b7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3700e39d-8cbf-5ad5-999c-73eff25385b7/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/cspURLViolation.md","size":961,"sha256":"9d09b9002343228773d670c6b1786a62343a70c1c271395ed40ddef054cbe02b","contentType":"text/markdown; charset=utf-8"},{"id":"5eee0ff9-fb77-57b5-874e-02e2c43ef222","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5eee0ff9-fb77-57b5-874e-02e2c43ef222/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/deprecation.md","size":45,"sha256":"d60bcd2546a8e3bb2df5882bcc358aecf439b23349269b5ef9d391f2e171c9a8","contentType":"text/markdown; charset=utf-8"},{"id":"a8dd5cc4-a612-5ef2-bad0-03a77d1d2ead","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a8dd5cc4-a612-5ef2-bad0-03a77d1d2ead/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md","size":57,"sha256":"a9f48b5beca24bd3800e8e2a50bee993f1a87e3e2a43f7b0440f785dbd89c4ae","contentType":"text/markdown; charset=utf-8"},{"id":"3abaae54-5517-5368-9a3a-8db6212b0f83","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3abaae54-5517-5368-9a3a-8db6212b0f83/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md","size":39,"sha256":"98212adec055b3fe4fa4cde8ad626e3215cfdb25c5492113e1d80903c62c7989","contentType":"text/markdown; charset=utf-8"},{"id":"b6a85dcc-8d67-59f0-947c-2e1e9efae296","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6a85dcc-8d67-59f0-947c-2e1e9efae296/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md","size":73,"sha256":"3c87f4734ed4d4bb920252f87148d86c7510dc4fb88920bfcd4c2188417404cb","contentType":"text/markdown; charset=utf-8"},{"id":"a4337f47-a112-575a-84c0-ae8b4c50d814","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4337f47-a112-575a-84c0-ae8b4c50d814/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md","size":37,"sha256":"33da807591a34454d0827de5af3347115f608d65d8927019b2cba9ec74850af3","contentType":"text/markdown; charset=utf-8"},{"id":"1bcf6ca7-c389-52f7-b7b9-52017e9f9800","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1bcf6ca7-c389-52f7-b7b9-52017e9f9800/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md","size":32,"sha256":"c65ab09e667a15b40005b67aa573c3e3873323ee2753575d0f1caf53c79ee4d0","contentType":"text/markdown; charset=utf-8"},{"id":"edd8c785-c139-5f81-9a3d-6c3d4371b77a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/edd8c785-c139-5f81-9a3d-6c3d4371b77a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataHttpNotFound.md","size":59,"sha256":"05d074faaf04b30c4e774a5c291f4556453f27c25be9dd1d6f87c60163a39561","contentType":"text/markdown; charset=utf-8"},{"id":"0d890605-c950-55d9-b7e4-d41d1f8690d4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0d890605-c950-55d9-b7e4-d41d1f8690d4/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataInvalidResponse.md","size":41,"sha256":"84a463ed5e06ac1d58dcaa316087705aaa1eaa757302ab4e710fd67b29bc9a5e","contentType":"text/markdown; charset=utf-8"},{"id":"09314a60-9bb8-5d51-83b2-8866d2d4d8e1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/09314a60-9bb8-5d51-83b2-8866d2d4d8e1/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataNoResponse.md","size":75,"sha256":"3b6570b33d55b86a129341ea19450d00c6fd616655c87dfba559fc6480cd58a5","contentType":"text/markdown; charset=utf-8"},{"id":"4c79ddf2-653e-5d45-bbe3-1055025bfffb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4c79ddf2-653e-5d45-bbe3-1055025bfffb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md","size":61,"sha256":"2c21dfbc6ac4c2dde7ce5db001bc411012dfc80389afeb82b3a5d77c9be1ce3a","contentType":"text/markdown; charset=utf-8"},{"id":"8f783ea7-9b63-5181-ab21-1f8f4df738f6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8f783ea7-9b63-5181-ab21-1f8f4df738f6/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md","size":28,"sha256":"64af01ff972d3eb65546dc43e18cd2a393eb74b309bb0dcc0467411bab5c68dc","contentType":"text/markdown; charset=utf-8"},{"id":"9943a085-c73a-5d04-bc4f-2903abffc2c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9943a085-c73a-5d04-bc4f-2903abffc2c8/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md","size":52,"sha256":"64287aaec8f7e5aa6f0bb01e796ea8ebdbe8886421d66dc1a30a18a089f1b0a1","contentType":"text/markdown; charset=utf-8"},{"id":"df3470bd-e151-5274-baca-fc1b865aab21","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/df3470bd-e151-5274-baca-fc1b865aab21/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md","size":41,"sha256":"b79a28eb80cbd8513c0a82a6453fc438c8f1e031aa4eda5150d574d52a4cde9d","contentType":"text/markdown; charset=utf-8"},{"id":"80b1fd7a-ea29-58f8-84aa-32009739e339","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80b1fd7a-ea29-58f8-84aa-32009739e339/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md","size":31,"sha256":"cd21fb1cb67c4c2e496bd91bb7a2a7dbf94f9bdb4dcea7e0f3ff44c17f43b7f4","contentType":"text/markdown; charset=utf-8"},{"id":"307aa783-1005-560e-a565-9b153fa9392a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/307aa783-1005-560e-a565-9b153fa9392a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md","size":65,"sha256":"d1b90cf2cffd497b5a533453061fed2ba0bd1e9eb31f36f278cdb41f204d2883","contentType":"text/markdown; charset=utf-8"},{"id":"5550459b-7973-5c60-9c56-9200dc3903de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5550459b-7973-5c60-9c56-9200dc3903de/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md","size":42,"sha256":"c8f4a5b4fc9a26b4a86bde8d638f0d477c2df7ba5b09da714dd75ae232ad17bc","contentType":"text/markdown; charset=utf-8"},{"id":"96fb7861-3b90-5222-90c1-46e56cb41907","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/96fb7861-3b90-5222-90c1-46e56cb41907/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md","size":63,"sha256":"f1d1bece78beb55e87ee015f9b7cc974bd7e527e90dc08eadcf1207182d251e0","contentType":"text/markdown; charset=utf-8"},{"id":"3531d36d-4980-558b-9532-f4c39561d9e3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3531d36d-4980-558b-9532-f4c39561d9e3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md","size":54,"sha256":"92660a3e8a294e2b706c3dd60bad61b25663198d7ebd672c95d80e3c336bcba4","contentType":"text/markdown; charset=utf-8"},{"id":"581304cd-565d-5bae-ba5e-875626504923","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/581304cd-565d-5bae-ba5e-875626504923/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md","size":88,"sha256":"c6348d0ded689311901dfe15c7de14ed852bccf8ccfe4df99d3bfd3e64039584","contentType":"text/markdown; charset=utf-8"},{"id":"f7de17ae-1d62-5b1d-973a-6b291fd1e7a4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f7de17ae-1d62-5b1d-973a-6b291fd1e7a4/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md","size":77,"sha256":"d6b491b4161ef7aab1f56b9ce87a0221c7a62ba30df6a2c1ae9a5a84327a9bc0","contentType":"text/markdown; charset=utf-8"},{"id":"02ea0e52-7cce-5272-87f4-aea313a66828","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/02ea0e52-7cce-5272-87f4-aea313a66828/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md","size":64,"sha256":"9c027ca2406363927574d19893cdcb2c0326d0dda70480cf227746738c778dbf","contentType":"text/markdown; charset=utf-8"},{"id":"0e468f11-558e-56f0-8736-5907bc37eff0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0e468f11-558e-56f0-8736-5907bc37eff0/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md","size":77,"sha256":"29ebfe081d61bf3e50dee9a49ab80557bee2c51365e2184f0d9f889dfa1aa152","contentType":"text/markdown; charset=utf-8"},{"id":"51312cd3-3aba-502c-8236-7a0bda4bed6c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/51312cd3-3aba-502c-8236-7a0bda4bed6c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md","size":99,"sha256":"e00c11a07131430712c97681e82d09a749b3e0156284793656ad8147a6bdbe50","contentType":"text/markdown; charset=utf-8"},{"id":"f3779d47-be67-5f40-9e69-656fdf739886","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f3779d47-be67-5f40-9e69-656fdf739886/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md","size":55,"sha256":"1ce2f0c309be96ad58e7c5858274793cdb1a9d7295cec65799ebcbda401eb00b","contentType":"text/markdown; charset=utf-8"},{"id":"36cc67ef-889e-57ff-a7b1-e2bf0b2831fd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/36cc67ef-889e-57ff-a7b1-e2bf0b2831fd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md","size":76,"sha256":"c15b185f4c9773658134846a71afa84f85f5c0e91c8a234f1f700dc8ad2a62f6","contentType":"text/markdown; charset=utf-8"},{"id":"704f8c2d-5525-53b4-b6d6-b136e72f0189","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/704f8c2d-5525-53b4-b6d6-b136e72f0189/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md","size":41,"sha256":"9c9a3d72f4fa4083914ac29f86dc4f617c4f609d78b96842082c7179544e4424","contentType":"text/markdown; charset=utf-8"},{"id":"137dd541-e908-56f8-9ef4-437482486628","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/137dd541-e908-56f8-9ef4-437482486628/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md","size":78,"sha256":"9cba668ffbffbbee3a3acdc41168ca3464b6d5422463b9b6f7330db75c4cd27c","contentType":"text/markdown; charset=utf-8"},{"id":"85b547a6-3120-5b7b-86ed-810fcdd530b7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/85b547a6-3120-5b7b-86ed-810fcdd530b7/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md","size":61,"sha256":"4fbadd451c6e3ea6b3bd925ca9dc7eaafa830697dff403d8a0672c523b6813e6","contentType":"text/markdown; charset=utf-8"},{"id":"499935fd-b10c-550e-9dc9-8a3b538633de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/499935fd-b10c-550e-9dc9-8a3b538633de/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md","size":74,"sha256":"32ff1a04f943d74cb8e5b96de3e92d4bab9d473be22e25f1dc9b6bf8ff0a9cab","contentType":"text/markdown; charset=utf-8"},{"id":"994e1529-02ab-5a96-9653-5ada5a027e23","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/994e1529-02ab-5a96-9653-5ada5a027e23/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md","size":409,"sha256":"f2d6f3a7558c28e36b6afb8b303f6d331cb340591f1bd8fbd7d1d8ec172cd5a6","contentType":"text/markdown; charset=utf-8"},{"id":"b975059d-e2bb-57fc-b6c3-f92ad1784edb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b975059d-e2bb-57fc-b6c3-f92ad1784edb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md","size":336,"sha256":"99e4da5dcc390bf4b57b8523aed1723810a452b6263eb196f959a4ae1d7df823","contentType":"text/markdown; charset=utf-8"},{"id":"043e52a0-5bfb-5c06-8e5f-cdaebc3588f2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/043e52a0-5bfb-5c06-8e5f-cdaebc3588f2/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md","size":219,"sha256":"dd7fdc95bf5f460865105b1d17b4afd9cba6c76d280a6de76a15a26a18cdf355","contentType":"text/markdown; charset=utf-8"},{"id":"9f188219-ab7f-55ab-881e-88b300ddace4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f188219-ab7f-55ab-881e-88b300ddace4/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md","size":268,"sha256":"ecb02d5d4aacd62caeb5a62580a68067f3cb606356885f58588e25b4eaab3766","contentType":"text/markdown; charset=utf-8"},{"id":"f760cc94-b261-5d15-bc23-0e0e574f2a0d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f760cc94-b261-5d15-bc23-0e0e574f2a0d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md","size":383,"sha256":"1855df199679949c83a5903ccda452d072d6757b70d4beb418b8fd8980fef8f9","contentType":"text/markdown; charset=utf-8"},{"id":"7358f799-5d31-5bf5-ac4a-8ce7499acf3f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7358f799-5d31-5bf5-ac4a-8ce7499acf3f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md","size":326,"sha256":"6fe4a2a22c5d102cacb78fd88fa4a427dcfa59e0cd2f38448cd9363415d1faf9","contentType":"text/markdown; charset=utf-8"},{"id":"63eb132f-b697-5da1-a196-d1caf39cbab5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/63eb132f-b697-5da1-a196-d1caf39cbab5/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md","size":245,"sha256":"8b2c6f83a84a42c0ae587b140e1cc99bcead60bc43819f993c7b855b395cde25","contentType":"text/markdown; charset=utf-8"},{"id":"5d750cf8-cbc0-53b2-b43a-fbebefe7f8e3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5d750cf8-cbc0-53b2-b43a-fbebefe7f8e3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md","size":361,"sha256":"187f129921b482b4518f9e5601fdc25e301531876b7a5e4a90d0483f9670a01b","contentType":"text/markdown; charset=utf-8"},{"id":"3119412d-d767-552c-b023-763de55f863a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3119412d-d767-552c-b023-763de55f863a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md","size":323,"sha256":"0d3bb548685fb25e20ca22ae9de9725c5fc06794f1efa8bad82592b26f0b521d","contentType":"text/markdown; charset=utf-8"},{"id":"536d95be-72cc-574c-951d-4f41cf80746c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/536d95be-72cc-574c-951d-4f41cf80746c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md","size":306,"sha256":"379682ccaa23c21a99120bd019b8c5f9a587cb41bea07cbd6f488048016c4543","contentType":"text/markdown; charset=utf-8"},{"id":"9caaec29-64a6-5de2-8737-ab23e4df0670","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9caaec29-64a6-5de2-8737-ab23e4df0670/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md","size":223,"sha256":"3b08393fbd2b4840369b36edfa2af24e7f8aee29670cd369a1234614a0a40733","contentType":"text/markdown; charset=utf-8"},{"id":"e76369a9-42ea-58f1-a843-cc0cc90850fb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e76369a9-42ea-58f1-a843-cc0cc90850fb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md","size":403,"sha256":"8a59e8912f6f32d6f16e5d90ebe7c2131c77b1a06b62400e6348350395ad2ee6","contentType":"text/markdown; charset=utf-8"},{"id":"4095cf0c-037e-5289-90c8-fc528c14f0a0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4095cf0c-037e-5289-90c8-fc528c14f0a0/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/genericResponseWasBlockedByORB.md","size":129,"sha256":"b5b766499df139bc5d98cfa22ccdc43f58dd99b3bf8b91a57f2d2e3cd7fe1da6","contentType":"text/markdown; charset=utf-8"},{"id":"ca6874ea-276e-502f-ab4b-62deeed8b948","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ca6874ea-276e-502f-ab4b-62deeed8b948/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/heavyAd.md","size":839,"sha256":"fa3c9512f87a35a1173a43fb84470db5b077d4d0cf820b2f946066bbb936314a","contentType":"text/markdown; charset=utf-8"},{"id":"f124a84c-58af-5fe0-97c3-22a16e1d021e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f124a84c-58af-5fe0-97c3-22a16e1d021e/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/mixedContent.md","size":429,"sha256":"5d4cd65a894905a032ab6d953c38c2b74ecf377e26ab0cf687dfa85c123260a5","contentType":"text/markdown; charset=utf-8"},{"id":"c8e04abc-0322-50b5-9a5c-47331e36e354","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c8e04abc-0322-50b5-9a5c-47331e36e354/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md","size":493,"sha256":"1c862f6cd8a4d5d19c7ab69312fe7e448f4d9d9fa0069a66b8b8fdab28f84370","contentType":"text/markdown; charset=utf-8"},{"id":"51906b45-b9ec-578e-b7f8-c7d31c45a02a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/51906b45-b9ec-578e-b7f8-c7d31c45a02a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md","size":388,"sha256":"76e8578b52eb7fdeedf7017696423c3ef1a48cf6d7aa97fdf39c71c73687ecf1","contentType":"text/markdown; charset=utf-8"},{"id":"7d5d2b1e-ef53-50b3-b49e-66b88c0d9a2a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7d5d2b1e-ef53-50b3-b49e-66b88c0d9a2a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md","size":446,"sha256":"cd72dde75f9f7392fd05f25c6477988b5ceef8e9ce30cf3e68c46667c78574b8","contentType":"text/markdown; charset=utf-8"},{"id":"7d69d121-3ac6-57b7-b08a-71d9500b23e3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7d69d121-3ac6-57b7-b08a-71d9500b23e3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md","size":499,"sha256":"91b5af99d1c598b2eceb11656153c1f64643f30cb0ea6751a4fac4716c70efe9","contentType":"text/markdown; charset=utf-8"},{"id":"a0b83c89-b54f-5ed0-bac0-69284f8a704e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a0b83c89-b54f-5ed0-bac0-69284f8a704e/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementCspFrameAncestorsMissing.md","size":278,"sha256":"aa41dc371ecdf99f249461da4e5e41259b5c53e89ce35e429026e85eb5f42482","contentType":"text/markdown; charset=utf-8"},{"id":"80b06d27-f56f-5d41-a2fe-2d2d132d7758","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80b06d27-f56f-5d41-a2fe-2d2d132d7758/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md","size":168,"sha256":"2c5e870bb7431a66058b430942865bf8ff0bbbd6b8343475dcbc6ed15f34f56c","contentType":"text/markdown; charset=utf-8"},{"id":"ec5836b3-f42e-5de8-8751-db7e0c1d988d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ec5836b3-f42e-5de8-8751-db7e0c1d988d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md","size":160,"sha256":"82ed938b144d85b5c748ad97495401f2cd2b52dd9f776e70ad9a528a02a800b0","contentType":"text/markdown; charset=utf-8"},{"id":"d4f44dab-a7ab-5c0b-8bda-abc54fa2a108","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d4f44dab-a7ab-5c0b-8bda-abc54fa2a108/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md","size":158,"sha256":"5342eeacfba50d23a89c685c309fb9d382b451f050e849d2f6ae461a41ba57a1","contentType":"text/markdown; charset=utf-8"},{"id":"60ee6cc6-3f6d-51c6-b0e0-058cd2ae3ce8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/60ee6cc6-3f6d-51c6-b0e0-058cd2ae3ce8/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementGeolocationDeprecated.md","size":146,"sha256":"0a769e2074573c9518d47b777ba4488d9fdccd30fdda184b62a80e931d97d43a","contentType":"text/markdown; charset=utf-8"},{"id":"eebd03a7-b03f-59b3-98c4-0110522c77bb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/eebd03a7-b03f-59b3-98c4-0110522c77bb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md","size":184,"sha256":"4a2d150682a4dcf838b813895c0c39174e4ea8d67367643df19dff52ff8028a7","contentType":"text/markdown; charset=utf-8"},{"id":"69c8ff9e-1f69-587f-a98b-25ad61a7412d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/69c8ff9e-1f69-587f-a98b-25ad61a7412d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md","size":232,"sha256":"18f5f6b70313be465d3ca026d060418fd4f71e97fb5093fa366ad94d3cc14bdc","contentType":"text/markdown; charset=utf-8"},{"id":"0935fdfd-f792-561f-85de-305e4adebd22","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0935fdfd-f792-561f-85de-305e4adebd22/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md","size":250,"sha256":"90c9bb5553d83cf592818249573769a4338fedb9e74e9f6a1887a79fe57313a6","contentType":"text/markdown; charset=utf-8"},{"id":"3e9e415c-e8c1-5b23-92d1-559a873d066c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e9e415c-e8c1-5b23-92d1-559a873d066c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementInvalidType.md","size":216,"sha256":"8845c74db1244719d19145d83a72f5f16e80cae93f56ceafb8e0f3e3042e4567","contentType":"text/markdown; charset=utf-8"},{"id":"baa2149f-7906-516f-901c-87056c8835ef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/baa2149f-7906-516f-901c-87056c8835ef/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md","size":282,"sha256":"f097a8dbf90a56efb67383a8b528cf8de1243d8ed5d5c6b3df8a8e5d4e06b2da","contentType":"text/markdown; charset=utf-8"},{"id":"d31df35c-1d75-50e8-9b9a-e99a9ffb5b8e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d31df35c-1d75-50e8-9b9a-e99a9ffb5b8e/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementLowContrast.md","size":218,"sha256":"2bbca6655ae900716815b7cc4711fd6e457c84ee84085156b1731c5d7600c75f","contentType":"text/markdown; charset=utf-8"},{"id":"038865b0-34a3-58bc-bc82-f1cc10639aa3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/038865b0-34a3-58bc-bc82-f1cc10639aa3/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md","size":218,"sha256":"33c284bb4215f8c0a966f825d534a9df845bcf6f70ebd2edb0536aa4fb1c55fe","contentType":"text/markdown; charset=utf-8"},{"id":"7fb0cbb0-f67f-5ad6-97d4-558c77510b93","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7fb0cbb0-f67f-5ad6-97d4-558c77510b93/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md","size":265,"sha256":"512496d3bbb51fc439132dc8df18d394fe71ae42a854d240b28968cb1913e5f9","contentType":"text/markdown; charset=utf-8"},{"id":"40d9fb8b-05a2-5340-a4eb-7f49d101f4a2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/40d9fb8b-05a2-5340-a4eb-7f49d101f4a2/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md","size":263,"sha256":"8aab2e68bb27aa4bc5717871179ef28e99252787bf1922203f341f2192b0c466","contentType":"text/markdown; charset=utf-8"},{"id":"eb4844a6-f1ca-5438-b26a-6aa071e17f10","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/eb4844a6-f1ca-5438-b26a-6aa071e17f10/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementPermissionsPolicyBlocked.md","size":265,"sha256":"febf50b4de141accd754b527af5fb4716cee032a9b3a7db907698fd38598c6f2","contentType":"text/markdown; charset=utf-8"},{"id":"db1c4c3c-8643-5645-9015-2e2d724f4e4b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/db1c4c3c-8643-5645-9015-2e2d724f4e4b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementRegistrationFailed.md","size":199,"sha256":"0dc0948932d2d0187e2feafdfe3e16be9d84dc5c0a1d468f2d8a9c000aadbf5f","contentType":"text/markdown; charset=utf-8"},{"id":"1307c374-7173-5be0-9180-cf7d44785a9e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1307c374-7173-5be0-9180-cf7d44785a9e/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md","size":373,"sha256":"89e96ca3c87f037061e79290c5004181c7de8cfacdce322db35e979f8ab111c0","contentType":"text/markdown; charset=utf-8"},{"id":"4fbe6722-c59c-5821-9afb-1bd12fba2f4b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4fbe6722-c59c-5821-9afb-1bd12fba2f4b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md","size":227,"sha256":"3aa62fec778d757bf9aa874017559f446eec1d9e62e686f3eacd44644a1a2d76","contentType":"text/markdown; charset=utf-8"},{"id":"b9d8186d-5b5c-5054-ab0c-d2dcfbf3dc05","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b9d8186d-5b5c-5054-ab0c-d2dcfbf3dc05/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md","size":294,"sha256":"9aef259d2c5d3e68291a31df084971f9f99c53b6183ebc9b80d3fa49183ac81d","contentType":"text/markdown; charset=utf-8"},{"id":"d39ad663-7316-574a-b087-0104333e662a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d39ad663-7316-574a-b087-0104333e662a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md","size":396,"sha256":"509597fff4c84684811254db87516c2c71f8257468c2a099b4e75d3dcf89995c","contentType":"text/markdown; charset=utf-8"},{"id":"525bb6c8-4b5e-52fc-a8dc-8bdb354f2a42","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/525bb6c8-4b5e-52fc-a8dc-8bdb354f2a42/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md","size":179,"sha256":"7b12a021366a0830f0d07e548b8fb03c2e3ed2b166812d393b787104bfeb23f2","contentType":"text/markdown; charset=utf-8"},{"id":"980fddac-b80e-5942-99a0-9895e1083141","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/980fddac-b80e-5942-99a0-9895e1083141/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/propertyRuleInvalidNameIssue.md","size":100,"sha256":"8e2233e96a1c8ab25e6550d991e9c9d0c4999d42f5550e6bc12da6aa36268da2","contentType":"text/markdown; charset=utf-8"},{"id":"b8262b64-0947-535f-ae3c-db03e05b0caa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b8262b64-0947-535f-ae3c-db03e05b0caa/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/propertyRuleIssue.md","size":157,"sha256":"db80a66f32437454cda966140bfc0f6782c5dc418916b0bbd16f311fc6756288","contentType":"text/markdown; charset=utf-8"},{"id":"e0af383a-58e3-55c9-968f-527bb1d18b8b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e0af383a-58e3-55c9-968f-527bb1d18b8b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md","size":587,"sha256":"a540f01ef8191409abf724524d616c772f0a18315eda60068856481103642604","contentType":"text/markdown; charset=utf-8"},{"id":"fe1e1de4-7415-5c5e-af55-d87fae3fcc17","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fe1e1de4-7415-5c5e-af55-d87fae3fcc17/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md","size":605,"sha256":"8be18b1c86e9bc6d98aa3884fc92a5daeb31e5e30bf6fade5bb691a87caf0e59","contentType":"text/markdown; charset=utf-8"},{"id":"67068530-723d-5fdc-a4af-44edc45c7d1b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/67068530-723d-5fdc-a4af-44edc45c7d1b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md","size":272,"sha256":"2718a77e28af39f3d7a2bed8490e82ef10ae4380791eda70ca6ae30f7f3dcdea","contentType":"text/markdown; charset=utf-8"},{"id":"f4f47767-3718-5fbe-821f-51fe4e5951f9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f4f47767-3718-5fbe-821f-51fe4e5951f9/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md","size":409,"sha256":"4c918a1c0c252de9064f81c84e29a3cb5a00fbb101686028876db35293f5dcb1","contentType":"text/markdown; charset=utf-8"},{"id":"45ffbdfb-6033-5b70-9126-491222d47108","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/45ffbdfb-6033-5b70-9126-491222d47108/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md","size":299,"sha256":"9338c9e0c3225680a26a63090bfdac604f3758637a09bcc654b4d78cccffdc0c","contentType":"text/markdown; charset=utf-8"},{"id":"48e18fb4-3fc4-5465-9406-ad00ed2ee772","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/48e18fb4-3fc4-5465-9406-ad00ed2ee772/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md","size":470,"sha256":"8999c6616176d2bb9dc93337a3308cc4f4820a7118add6f8b3a41966ab42f0de","contentType":"text/markdown; charset=utf-8"},{"id":"ef853d4b-d539-5f7e-b25b-202b2e6b20f1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ef853d4b-d539-5f7e-b25b-202b2e6b20f1/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedArrayBuffer.md","size":643,"sha256":"7a8b9aea521095f976fdf930583467e10cfee50c5619750b4e4ff67bd9a09455","contentType":"text/markdown; charset=utf-8"},{"id":"ac202d06-a3e2-5aab-945d-044346f8212d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ac202d06-a3e2-5aab-945d-044346f8212d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md","size":101,"sha256":"ab700beb8d669d83e8d2c6bd7bbe7a9291b21f4a03eda4dece880eafdcf5924f","contentType":"text/markdown; charset=utf-8"},{"id":"56028f1b-23f6-58ae-a9dc-3405dcf3a78c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/56028f1b-23f6-58ae-a9dc-3405dcf3a78c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorDictionaryLoadFailure.md","size":97,"sha256":"679afe8f7cf9f3fa74380f7be129f8cc59ca82459e3ff6c0433e25cf0ded3533","contentType":"text/markdown; charset=utf-8"},{"id":"4296730d-ebbb-54f5-b91c-81acb4f060b5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4296730d-ebbb-54f5-b91c-81acb4f060b5/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md","size":155,"sha256":"ad9f290c0d93730b841fd08d7b9d85dab654e8398370f7071062a97ee3584e8b","contentType":"text/markdown; charset=utf-8"},{"id":"2d77ae90-ff09-5589-baf8-fa56d6bd66cb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2d77ae90-ff09-5589-baf8-fa56d6bd66cb/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md","size":101,"sha256":"6f4afdd3f10833d8c40c63ed99c5d637ecc23944c2e447c6c7a19c019871d3c3","contentType":"text/markdown; charset=utf-8"},{"id":"bbd954ea-f793-59b1-ae55-f2cfb4b17705","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bbd954ea-f793-59b1-ae55-f2cfb4b17705/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md","size":91,"sha256":"119de94f8da9e08732a8563ec0e88c31218b51e7b41c3fb802f156e23f0de58b","contentType":"text/markdown; charset=utf-8"},{"id":"efb96964-8d7d-5eaa-b8b1-f9887957c9fd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/efb96964-8d7d-5eaa-b8b1-f9887957c9fd/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md","size":49,"sha256":"32b8fae60e3c9253b16f88223c55be28c78c6fecf4b1e7e8f7edd2b4911759d6","contentType":"text/markdown; charset=utf-8"},{"id":"f846ca3e-bb15-51be-abc3-448ba0588397","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f846ca3e-bb15-51be-abc3-448ba0588397/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md","size":124,"sha256":"8f3b268b6f6859e70609890a54608fee6a09c6a2dc51e8badcf710dd0ae95f09","contentType":"text/markdown; charset=utf-8"},{"id":"5dced226-393d-5e29-baf0-371ed0d24236","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5dced226-393d-5e29-baf0-371ed0d24236/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md","size":126,"sha256":"dc924406fb62cc658fe14c00ac2c7560b3117e7c52e59fb039a987c5aaa98991","contentType":"text/markdown; charset=utf-8"},{"id":"ad930de6-cc56-5c41-83bd-06024d6fcb85","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ad930de6-cc56-5c41-83bd-06024d6fcb85/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInsufficientResources.md","size":63,"sha256":"5639d01f050cf720d625e1bb1c39c58c047346f945d0ed8ba9324e45ebe5b4d6","contentType":"text/markdown; charset=utf-8"},{"id":"5bfca06c-69a1-564c-9d63-a58abd616b72","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5bfca06c-69a1-564c-9d63-a58abd616b72/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidMatchField.md","size":84,"sha256":"a05195c09633c501f833bef808e79e3ee092449e0138946d93db49e96f3c4fdd","contentType":"text/markdown; charset=utf-8"},{"id":"372c82a0-647e-5ccf-9bba-1d9a8e7fcae1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/372c82a0-647e-5ccf-9bba-1d9a8e7fcae1/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md","size":81,"sha256":"c402e57c91007c83458fb122655e64f82f945d0dca03b35c82a67977b6883049","contentType":"text/markdown; charset=utf-8"},{"id":"4cbe51a1-7750-5447-841b-fd216bafdd14","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4cbe51a1-7750-5447-841b-fd216bafdd14/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidTTLField.md","size":93,"sha256":"d82a2fef3b1e5b24113ccd860241dbc91e6f69074ac52f588758db5b332d2e6e","contentType":"text/markdown; charset=utf-8"},{"id":"b9891892-8fda-574b-ae27-d24edc423795","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b9891892-8fda-574b-ae27-d24edc423795/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md","size":138,"sha256":"ebf64be141071543773c977c8c7f0d7c7afeddef942154ea4efed366c956a76c","contentType":"text/markdown; charset=utf-8"},{"id":"6ec6b7be-cca8-594f-a275-b74587f13db5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6ec6b7be-cca8-594f-a275-b74587f13db5/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoMatchField.md","size":76,"sha256":"5f79e6502e160c9a0314c65a2400746f622e4f77351f578da378f06839462a58","contentType":"text/markdown; charset=utf-8"},{"id":"4d20293f-e97e-5bd6-abc7-69913d52da9c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4d20293f-e97e-5bd6-abc7-69913d52da9c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonIntegerTTLField.md","size":85,"sha256":"1e9f50eab73847e93606125f0ada72880605fabe643bce78723d73b108196696","contentType":"text/markdown; charset=utf-8"},{"id":"0886f0d9-4abd-5d23-bd0c-43e43fb9317d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0886f0d9-4abd-5d23-bd0c-43e43fb9317d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonListMatchDestField.md","size":88,"sha256":"a4ed1b68b35712b9faf37ea4ab65886b56b4c5dbe38e83132c4f5b55d5a383b0","contentType":"text/markdown; charset=utf-8"},{"id":"cb766b9c-9df0-5d29-8b28-24acbd5e8a94","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb766b9c-9df0-5d29-8b28-24acbd5e8a94/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonSecureContext.md","size":115,"sha256":"05967322f1276d5c9118002c40db404d4d25faea667773fc4da77c118f40a4a7","contentType":"text/markdown; charset=utf-8"},{"id":"ac652d74-d243-57e8-acd5-ce4444490914","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ac652d74-d243-57e8-acd5-ce4444490914/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringIdField.md","size":82,"sha256":"15e917c50820c54bbce9001e0a902b6bea3248cf50e03d7fba5557691123be20","contentType":"text/markdown; charset=utf-8"},{"id":"460287ce-ed31-5350-8037-4cc466faa749","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/460287ce-ed31-5350-8037-4cc466faa749/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringInMatchDestList.md","size":98,"sha256":"06339e2c226148f79598b22f3dc75a5428c13c280944dbaf08165aae5ef53b42","contentType":"text/markdown; charset=utf-8"},{"id":"d35e6f90-c00e-5ca9-90bb-9a81ece10e24","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d35e6f90-c00e-5ca9-90bb-9a81ece10e24/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringMatchField.md","size":85,"sha256":"70ca0b73f628718a040ce61c58317cafc07f8bd99fc69a75657d2c04aa75e778","contentType":"text/markdown; charset=utf-8"},{"id":"694f0b4b-9b8e-5cce-9ec4-c0f00f29bb90","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/694f0b4b-9b8e-5cce-9ec4-c0f00f29bb90/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonTokenTypeField.md","size":83,"sha256":"9c1b26613feaa0ff7419aeaff68ebec250f88c128f330789652b2d11c18567c6","contentType":"text/markdown; charset=utf-8"},{"id":"a23e446b-9963-555a-984c-50d57357a154","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a23e446b-9963-555a-984c-50d57357a154/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorRequestAborted.md","size":49,"sha256":"d756b261280a632fbadc83619639e72d4c4f0ac62659d5311bdce3ce57c81ae4","contentType":"text/markdown; charset=utf-8"},{"id":"d33185fa-f81a-5b1d-9c2c-7f0ddbb18712","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d33185fa-f81a-5b1d-9c2c-7f0ddbb18712/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorShuttingDown.md","size":49,"sha256":"5b535c320f04e9070d114b8611abb705ea506ba56c751d8a88aa61e1edf8f3e7","contentType":"text/markdown; charset=utf-8"},{"id":"35186980-018e-53d8-89ab-e5da14f49e7b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/35186980-018e-53d8-89ab-e5da14f49e7b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorTooLongIdField.md","size":134,"sha256":"1c6fb6b1cfeb31363b0830ffd78321901671827468da1cfa4a2d2e2883aae552","contentType":"text/markdown; charset=utf-8"},{"id":"86b084ee-df3f-5083-95d1-e4ed597fdb31","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/86b084ee-df3f-5083-95d1-e4ed597fdb31/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorUnsupportedType.md","size":131,"sha256":"ba2b6018786a9f13650f202872dbdbfb27e95a36c37212b88205e3a6ebdc8edd","contentType":"text/markdown; charset=utf-8"},{"id":"fef535f3-98c6-563b-995e-d1902e9266ac","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fef535f3-98c6-563b-995e-d1902e9266ac/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md","size":655,"sha256":"90e5ca6d5912b03dda80b986a02fd7bdfc435b5982ff6812ceb5f6815baf0345","contentType":"text/markdown; charset=utf-8"},{"id":"d63f4c5c-9459-5ff7-b712-74458db88aba","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d63f4c5c-9459-5ff7-b712-74458db88aba/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md","size":756,"sha256":"90dab69bc3154fbb6aa82feb897664d4484e87fe87550f96e6fd282588db449f","contentType":"text/markdown; charset=utf-8"},{"id":"ffd1dd17-88cb-5423-8648-84c7a53bd55c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ffd1dd17-88cb-5423-8648-84c7a53bd55c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md","size":465,"sha256":"298619753779a6ac22dbf212ce304e258f9c2c2082cbf238e05a681881042e32","contentType":"text/markdown; charset=utf-8"},{"id":"6f634ad2-53d9-5eca-9e98-435888b8143c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6f634ad2-53d9-5eca-9e98-435888b8143c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md","size":477,"sha256":"e110474144b928686d58e98331535cd1651a971e0448673fcd78d09f670fae30","contentType":"text/markdown; charset=utf-8"},{"id":"0f715bcb-3678-5038-b618-eb6df9bfcf1a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0f715bcb-3678-5038-b618-eb6df9bfcf1a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md","size":420,"sha256":"8c3241a2afbbaab4a66da65f06a44a232eabad57fea83881751c074ec1d24f20","contentType":"text/markdown; charset=utf-8"},{"id":"6e0f375c-0a7f-5c1c-aed0-a499012a9d25","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6e0f375c-0a7f-5c1c-aed0-a499012a9d25/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md","size":643,"sha256":"07aa041e51fcb9a8d37748c66989846e839f260ca63ac8b54cfd66a1b98294c6","contentType":"text/markdown; charset=utf-8"},{"id":"4cbe94ab-e1ad-5e05-aa67-04b034a0e974","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4cbe94ab-e1ad-5e05-aa67-04b034a0e974/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md","size":678,"sha256":"c0202a918ce81e9349dae97ab86df55f02d26ce01b619716905fcbd028ed1b70","contentType":"text/markdown; charset=utf-8"},{"id":"3c21877d-5a9e-520c-a39e-8dbd55714c11","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3c21877d-5a9e-520c-a39e-8dbd55714c11/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md","size":278,"sha256":"d0701302767b90a87b607bcb1abafb22749b12dbc3ba497a81b2a8b1232f06a5","contentType":"text/markdown; charset=utf-8"},{"id":"df68fdcf-61d6-5fb5-a118-002ae1f1e2df","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/df68fdcf-61d6-5fb5-a118-002ae1f1e2df/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md","size":516,"sha256":"3d58bd4b78f0b8805de59c285d93036cf5b911f459a1c5de7049c3e338fdcb2c","contentType":"text/markdown; charset=utf-8"},{"id":"bb8aca50-b626-505c-b273-7227617fdaff","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bb8aca50-b626-505c-b273-7227617fdaff/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md","size":231,"sha256":"aa108736ae9ed2f5c3689cabcf5df2f752359f1f7a144f6150fc9ff59cffeefa","contentType":"text/markdown; charset=utf-8"},{"id":"5f4f9539-3076-5893-9c01-5986edfc52b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5f4f9539-3076-5893-9c01-5986edfc52b6/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md","size":286,"sha256":"dec937d8b607fcc62f0c25a0e9e15fe81346396d7e8a14bfb486f85749b56af2","contentType":"text/markdown; charset=utf-8"},{"id":"1e5d880f-4d25-5a1a-8a8e-561b64daee9f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1e5d880f-4d25-5a1a-8a8e-561b64daee9f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md","size":338,"sha256":"d66bca339dfc3987fec6632446bc87837fb7d2b1c1f7058d4389187f023a86e5","contentType":"text/markdown; charset=utf-8"},{"id":"5ed4666a-f4da-54e3-9990-caf6150030ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ed4666a-f4da-54e3-9990-caf6150030ca/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md","size":500,"sha256":"f8754b89bad0c8c966834f728bad0e3d09df84fcfc56711cfe1cdfadc83bd7e8","contentType":"text/markdown; charset=utf-8"},{"id":"bd8de630-2f52-50c1-b4a6-ea7d7fe721aa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bd8de630-2f52-50c1-b4a6-ea7d7fe721aa/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md","size":339,"sha256":"8c081f96113b000b19d42b6c3f2d8e93104700e370dbb831b9059a299a1a811b","contentType":"text/markdown; charset=utf-8"},{"id":"dd1123da-63a3-54cc-abc7-47e26e8dc37d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dd1123da-63a3-54cc-abc7-47e26e8dc37d/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md","size":380,"sha256":"6dda3a7dd92ec429740427d66a8b3fdd50f5f763ad05a308e4a548deafb4f481","contentType":"text/markdown; charset=utf-8"},{"id":"45ba12b7-50e6-5f17-9ce5-1c722cedfb2a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/45ba12b7-50e6-5f17-9ce5-1c722cedfb2a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md","size":351,"sha256":"a69b299635facb6d5926e164b4cbc1fd100d31a4c0eb3eb4555a4ed60567f86c","contentType":"text/markdown; charset=utf-8"},{"id":"34bdd15b-5a15-5cb3-9deb-9bbdb557a98c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34bdd15b-5a15-5cb3-9deb-9bbdb557a98c/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md","size":429,"sha256":"2d249b1f990f05571ace221e3aea5d268ecd7777ffe0cb7cdaeb5cf8d67ad328","contentType":"text/markdown; charset=utf-8"},{"id":"0d73bccf-091b-5a6d-b1c5-118320895b0a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0d73bccf-091b-5a6d-b1c5-118320895b0a/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md","size":429,"sha256":"f49b48eeea0ed04fe9c259e16d5bc015c60d00dd7fc4dd1a63caaa8b7319343b","contentType":"text/markdown; charset=utf-8"},{"id":"1905502b-f17e-5106-b74f-728a4509e82f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1905502b-f17e-5106-b74f-728a4509e82f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md","size":259,"sha256":"3635b3cf8002ea1d227aa3e97ee0766ed490e7a3599e30368fd22e996b3ff74c","contentType":"text/markdown; charset=utf-8"},{"id":"f5f984ed-a618-505e-84ed-f29c3b9e974b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f5f984ed-a618-505e-84ed-f29c3b9e974b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md","size":295,"sha256":"fb26c69d37592e89c092bf15fa33a2aaebd141e63c28b67380c0f00176be6995","contentType":"text/markdown; charset=utf-8"},{"id":"e5c9b615-3575-50fb-b6e3-8ee68926ad90","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e5c9b615-3575-50fb-b6e3-8ee68926ad90/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md","size":384,"sha256":"80b0221713f098d37666e4974393db052d3b4e3804762142fa6d195ba289d963","contentType":"text/markdown; charset=utf-8"},{"id":"8d3be8e6-d1ef-518d-8156-d7fc62c82f8b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8d3be8e6-d1ef-518d-8156-d7fc62c82f8b/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/stylesheetLateImport.md","size":266,"sha256":"e70bfc6a1cd1bea90c85daac74f9958c849c95cc1266d6d91743a5ca65e81400","contentType":"text/markdown; charset=utf-8"},{"id":"aa6167e3-b2e0-58a3-afba-8953028894df","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aa6167e3-b2e0-58a3-afba-8953028894df/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/stylesheetRequestFailed.md","size":76,"sha256":"4e27065ffadffd1630c762f189bc7ed842c52a1631225ae8baffd4598135f900","contentType":"text/markdown; charset=utf-8"},{"id":"827561dc-50db-5426-b4f6-fb325fface50","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/827561dc-50db-5426-b4f6-fb325fface50/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md","size":231,"sha256":"2ddd11fab591308e48620953548cfd7503ef62ce48b962cbae3c57676039ce0e","contentType":"text/markdown; charset=utf-8"},{"id":"c1d71d86-9dec-5f84-ae2b-bb1177cbde61","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c1d71d86-9dec-5f84-ae2b-bb1177cbde61/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md","size":606,"sha256":"f92ab3fa21768c2908cf7d6eeb0460fff1a4b31d2990e0f172b0536225c8a851","contentType":"text/markdown; charset=utf-8"},{"id":"7b3ba25a-c1b6-5fde-b049-3352d315f1b2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7b3ba25a-c1b6-5fde-b049-3352d315f1b2/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md","size":692,"sha256":"ddc812a8cf515ba7d767a844ceeec99f4d7715098b0691c7ec4d488f49f6b366","contentType":"text/markdown; charset=utf-8"},{"id":"0786e473-7b88-50dc-800b-4a40f35cb8e4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0786e473-7b88-50dc-800b-4a40f35cb8e4/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md","size":549,"sha256":"f694c4e6b741d57f0d4645df98d626fae857f4bc48c61d08bb22ec8eaec7e18a","contentType":"text/markdown; charset=utf-8"},{"id":"5794f4c1-0fdd-55b3-8512-5e2459e4e51f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5794f4c1-0fdd-55b3-8512-5e2459e4e51f/attachment.md","path":"config/servers/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md","size":581,"sha256":"057563cbf358c9592b679a4de072335e00534c89cc182125833ef4e0b27d9c33","contentType":"text/markdown; charset=utf-8"},{"id":"94ea9145-3f1b-5469-9f05-f38a95f93ac3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/94ea9145-3f1b-5469-9f05-f38a95f93ac3/attachment.js","path":"config/servers/build/src/tools/ToolDefinition.js","size":602,"sha256":"8ac66eee3d2358d156b6cae5d525585c04144d47d28b9c279e7ecf22fdab1a6c","contentType":"application/javascript; charset=utf-8"},{"id":"6608450c-e1a4-5990-b3d0-cd4b3873c97f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6608450c-e1a4-5990-b3d0-cd4b3873c97f/attachment.js","path":"config/servers/build/src/tools/categories.js","size":746,"sha256":"eb1e31f512362e5a5331094532f8f6f7ad3626fe4d9d1efda85acc9a1949ff38","contentType":"application/javascript; charset=utf-8"},{"id":"241cae26-5a7c-55f1-93f1-df6fd9c7829d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/241cae26-5a7c-55f1-93f1-df6fd9c7829d/attachment.js","path":"config/servers/build/src/tools/console.js","size":2634,"sha256":"a21c21e9bcdc63778d211bef2f993e8ae753a304ed6a43c7cea8fe726d4e3da8","contentType":"application/javascript; charset=utf-8"},{"id":"d9af82a1-6c61-5621-b9de-84d7a233e075","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d9af82a1-6c61-5621-b9de-84d7a233e075/attachment.js","path":"config/servers/build/src/tools/emulation.js","size":3229,"sha256":"1ba9361a8d4b5c6ab6ed06efc35a5ba88a7748f83c57336983af084213c0e59f","contentType":"application/javascript; charset=utf-8"},{"id":"c62a39e2-868c-55b2-99b9-85fbfb5d3260","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c62a39e2-868c-55b2-99b9-85fbfb5d3260/attachment.js","path":"config/servers/build/src/tools/input.js","size":10069,"sha256":"6b1f972e84ab90b0a29669803848aa29776bb75387203e792ca38f026a5b0bf8","contentType":"application/javascript; charset=utf-8"},{"id":"5e8ff539-124a-50ef-a150-229ab90c2ed7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5e8ff539-124a-50ef-a150-229ab90c2ed7/attachment.js","path":"config/servers/build/src/tools/network.js","size":3596,"sha256":"56754fb574d36cee773d39a367c2196f74c0ac5eff875997dc1819e0dd405a1a","contentType":"application/javascript; charset=utf-8"},{"id":"7ba97eb5-f6b5-5450-84f6-a12c3fc8d7a6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7ba97eb5-f6b5-5450-84f6-a12c3fc8d7a6/attachment.js","path":"config/servers/build/src/tools/pages.js","size":8585,"sha256":"9f37ea9a4fbed7d4c5fb825c08592806141a694d30dd81638fee764c60805a42","contentType":"application/javascript; charset=utf-8"},{"id":"e8dc4e39-0676-55df-bf88-7ac82953474e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e8dc4e39-0676-55df-bf88-7ac82953474e/attachment.js","path":"config/servers/build/src/tools/performance.js","size":6378,"sha256":"303cc7c7187ff000bff0bb4603d75074a653f9d24f7707e2df3cc213357c3a6f","contentType":"application/javascript; charset=utf-8"},{"id":"c4e9cb6c-445d-5578-8664-8e9b0ad17525","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c4e9cb6c-445d-5578-8664-8e9b0ad17525/attachment.js","path":"config/servers/build/src/tools/screenshot.js","size":3501,"sha256":"38ec009345141ca4e23e2402eae3d945874115b4c31325dcd7bc8d873aaec305","contentType":"application/javascript; charset=utf-8"},{"id":"b24aa053-75f3-5f85-b6f5-4ff53da7031d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b24aa053-75f3-5f85-b6f5-4ff53da7031d/attachment.js","path":"config/servers/build/src/tools/script.js","size":2715,"sha256":"529e25b855e7987c84e73a3b0100df2ff9c39e4e6c22c1d27447080581974b7a","contentType":"application/javascript; charset=utf-8"},{"id":"6b6d7b86-5d0e-5bf7-bb59-664c9b778ac4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6b6d7b86-5d0e-5bf7-bb59-664c9b778ac4/attachment.js","path":"config/servers/build/src/tools/snapshot.js","size":2065,"sha256":"830f100d8bd68f313907997917056e47d785d1a076bda3621b10b9080eb3c9b8","contentType":"application/javascript; charset=utf-8"},{"id":"520473d0-5047-569f-b032-f4fd5c49ca7a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/520473d0-5047-569f-b032-f4fd5c49ca7a/attachment.js","path":"config/servers/build/src/tools/tools.js","size":946,"sha256":"c36a6ee293eaae6b47fd57fe0c22fdda26a8ec6d1b5a7208b352d03894e0daec","contentType":"application/javascript; charset=utf-8"},{"id":"5effefd0-5318-5efe-9893-e3f76b6f9a98","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5effefd0-5318-5efe-9893-e3f76b6f9a98/attachment.js","path":"config/servers/build/src/trace-processing/parse.js","size":3040,"sha256":"bce226f90a6e4d4ceb74b5cfd7e72db600743fdafb2a583776585eafb8bab6c6","contentType":"application/javascript; charset=utf-8"},{"id":"99a1d482-efbc-52ce-8eff-fa050c30ba3a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/99a1d482-efbc-52ce-8eff-fa050c30ba3a/attachment.js","path":"config/servers/build/src/utils/keyboard.js","size":4244,"sha256":"87ab08e525230c16000acee9c32db9870402ebbac4e29b12564515e769c42017","contentType":"application/javascript; charset=utf-8"},{"id":"20f4f885-fe6f-5e78-944f-a18a2b0c17f9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/20f4f885-fe6f-5e78-944f-a18a2b0c17f9/attachment.js","path":"config/servers/build/src/utils/pagination.js","size":1560,"sha256":"e75e57ec4fed6ecb7ecfe95a805c5455ecee75627491819854f9c9013da36bcf","contentType":"application/javascript; charset=utf-8"},{"id":"e604f588-799c-5934-960f-dc4bceb19e3d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e604f588-799c-5934-960f-dc4bceb19e3d/attachment.js","path":"config/servers/build/src/utils/types.js","size":99,"sha256":"72bf8b9a9edc2e254f103bf9856198e01d14b747b2410846112212534bef0009","contentType":"application/javascript; charset=utf-8"},{"id":"5811b333-1989-51d4-b5cb-77ea8e3ada2a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5811b333-1989-51d4-b5cb-77ea8e3ada2a/attachment.js","path":"config/servers/build/tests/DevtoolsUtils.test.js","size":7928,"sha256":"6fc59facee46e09330712b5510ac4d7d40205813c6ef108bef221432772e50a6","contentType":"application/javascript; charset=utf-8"},{"id":"770b0882-c758-5965-ba2f-83696d1bd552","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/770b0882-c758-5965-ba2f-83696d1bd552/attachment.js","path":"config/servers/build/tests/McpContext.test.js","size":3596,"sha256":"aec0a7e65f9d539bfe5203bdd7f942b695e07ac1fa125d716fb178ecad968ee1","contentType":"application/javascript; charset=utf-8"},{"id":"812fb45b-071f-5807-9d07-d8fe84803c21","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/812fb45b-071f-5807-9d07-d8fe84803c21/attachment.js","path":"config/servers/build/tests/McpResponse.test.js","size":19819,"sha256":"c46a7952ac04b2f1b8dedfdaa5757c65d44a1b9038e600e7ef2f11b849590007","contentType":"application/javascript; charset=utf-8"},{"id":"077a1df7-529f-5da7-952d-27a1bab30f2e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/077a1df7-529f-5da7-952d-27a1bab30f2e/attachment.js","path":"config/servers/build/tests/PageCollector.test.js","size":12366,"sha256":"87c852a2173c595a6503e1a2817d25bff850005eaaec3f5a0e45349044211901","contentType":"application/javascript; charset=utf-8"},{"id":"21e587ac-02ab-542e-be80-f3ea2d911033","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/21e587ac-02ab-542e-be80-f3ea2d911033/attachment.js","path":"config/servers/build/tests/browser.test.js","size":3223,"sha256":"c6ac23fb8db378d82dd24801366d782edfb0c98d4e49c0b1b290f9854a6a86fd","contentType":"application/javascript; charset=utf-8"},{"id":"9ef5a390-857d-5fb4-ac05-74f499008f2f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9ef5a390-857d-5fb4-ac05-74f499008f2f/attachment.js","path":"config/servers/build/tests/cli.test.js","size":6812,"sha256":"b70f0c1924f7d8eba53cad10784bb84991f7f5a7e0bdbc7d7075ef9107a74c40","contentType":"application/javascript; charset=utf-8"},{"id":"f9a2a4c9-e608-5c6e-942d-11b9f36c0f6d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f9a2a4c9-e608-5c6e-942d-11b9f36c0f6d/attachment.js","path":"config/servers/build/tests/formatters/consoleFormatter.test.js","size":4362,"sha256":"d8679c5d07d10896c65841f4dd0a990ce594c36a5e0dcc9548e6dbd917671d80","contentType":"application/javascript; charset=utf-8"},{"id":"08be5f5e-e66c-5182-b11b-8e829b828dc4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/08be5f5e-e66c-5182-b11b-8e829b828dc4/attachment.js","path":"config/servers/build/tests/formatters/networkFormatter.test.js","size":8494,"sha256":"dd4e4b8bdd9a0dfd1697a54320d6634c5fe435225b9f3e4569103cb49fac8dc5","contentType":"application/javascript; charset=utf-8"},{"id":"c2cc091d-1e26-5d60-94a5-fa560b5c80be","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c2cc091d-1e26-5d60-94a5-fa560b5c80be/attachment.js","path":"config/servers/build/tests/formatters/snapshotFormatter.test.js","size":6561,"sha256":"da4ae53a26b7dad5d918554174fb407bce54c13de038fea7e4b14a00010d7cbf","contentType":"application/javascript; charset=utf-8"},{"id":"ea607c4c-4511-56f9-a8b5-12d881388616","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea607c4c-4511-56f9-a8b5-12d881388616/attachment.js","path":"config/servers/build/tests/index.test.js","size":3099,"sha256":"5e1df26958729f89afe6f8bfde60aacce6f91e963195620a555594ceb8cf271e","contentType":"application/javascript; charset=utf-8"},{"id":"e15d412e-81a1-5801-aa08-203dc0fdb215","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e15d412e-81a1-5801-aa08-203dc0fdb215/attachment.js","path":"config/servers/build/tests/server.js","size":2722,"sha256":"8cd941c02320a15794d6222b13bf7233cd8db5a4a0122ea43f8b151139f2375d","contentType":"application/javascript; charset=utf-8"},{"id":"d128a634-178a-56fd-8274-9957ae97b275","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d128a634-178a-56fd-8274-9957ae97b275/attachment.js","path":"config/servers/build/tests/setup.js","size":980,"sha256":"7071e9f8a36bc66737b4cd6a49d2754ba76b89059511a786642392e8f855433d","contentType":"application/javascript; charset=utf-8"},{"id":"ab015946-8050-56ce-9ccf-610e183bde9c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ab015946-8050-56ce-9ccf-610e183bde9c/attachment.js","path":"config/servers/build/tests/snapshot.js","size":388,"sha256":"e5e0d0887026ce8253ff6a3e07f7bedd20443c3558f3553e3d9fee00bc3ca7dc","contentType":"application/javascript; charset=utf-8"},{"id":"45965f20-b3af-5d7e-ba6c-9d6e1f64a041","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/45965f20-b3af-5d7e-ba6c-9d6e1f64a041/attachment.js","path":"config/servers/build/tests/tools/console.test.js","size":9410,"sha256":"39ac3b08b7b4ea76f9792a7143a26fcb391463f3707cd41449d0cbd74969f04e","contentType":"application/javascript; charset=utf-8"},{"id":"f20d1882-e8bc-54e0-897e-1c2a576d00c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f20d1882-e8bc-54e0-897e-1c2a576d00c8/attachment.js","path":"config/servers/build/tests/tools/emulation.test.js","size":6658,"sha256":"8ecb33ba0f9b463b9290eb697c0d6457038b7e6990882c1812d501f153bbbc12","contentType":"application/javascript; charset=utf-8"},{"id":"187ba848-6125-5ca3-8f72-bcd439d27a87","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/187ba848-6125-5ca3-8f72-bcd439d27a87/attachment.js","path":"config/servers/build/tests/tools/input.test.js","size":16303,"sha256":"e0bc224a257691656452eff6de9c9d9f416231126f3507139d30ffe1519a1c66","contentType":"application/javascript; charset=utf-8"},{"id":"8a579962-6360-5171-8445-4fd627af8a73","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8a579962-6360-5171-8445-4fd627af8a73/attachment.js","path":"config/servers/build/tests/tools/network.test.js","size":6024,"sha256":"74c06131a2c4a4caabc1d16ad28d9ad15e5410114926937ebf98644dca69796e","contentType":"application/javascript; charset=utf-8"},{"id":"3303f8a9-bf50-5c35-a7ee-dfd7946be9dd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3303f8a9-bf50-5c35-a7ee-dfd7946be9dd/attachment.js","path":"config/servers/build/tests/tools/pages.test.js","size":10895,"sha256":"04aad6750fd5e73ca3103433cc9144078d081362fbc87662ed39bf569d530ed5","contentType":"application/javascript; charset=utf-8"},{"id":"3a1ae7e9-0413-5a52-ac22-3eef1ca75e54","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3a1ae7e9-0413-5a52-ac22-3eef1ca75e54/attachment.js","path":"config/servers/build/tests/tools/performance.test.js","size":10389,"sha256":"139a4e5ba33c5a682a79e80390f7d102000a7193679f29f7d07b28645f88fc5e","contentType":"application/javascript; charset=utf-8"},{"id":"93016065-f7a4-5cd2-a50f-bd616d1423a4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/93016065-f7a4-5cd2-a50f-bd616d1423a4/attachment.js","path":"config/servers/build/tests/tools/screenshot.test.js","size":8954,"sha256":"9c5c2c7c40b3eaf56683ce864559b0447d0d01d99d827e8aea805c63ab46aa99","contentType":"application/javascript; charset=utf-8"},{"id":"4770b361-c1a0-5989-b6c8-6447596f3014","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4770b361-c1a0-5989-b6c8-6447596f3014/attachment.js","path":"config/servers/build/tests/tools/script.test.js","size":5969,"sha256":"d132cea325cbd6aed5ee6b1db3835dbd84664bb96fba6e64afb3bd38c25646fc","contentType":"application/javascript; charset=utf-8"},{"id":"0a05e3ed-7f25-5d3a-8474-c540e202538a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0a05e3ed-7f25-5d3a-8474-c540e202538a/attachment.js","path":"config/servers/build/tests/tools/snapshot.test.js","size":3294,"sha256":"725ea4ecd6493c49d861fe919afb1e8972dfa24defb79178cab521576948e4d9","contentType":"application/javascript; charset=utf-8"},{"id":"96f2b19f-f1ee-5cb5-b6d7-62fb74df71aa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/96f2b19f-f1ee-5cb5-b6d7-62fb74df71aa/attachment.js","path":"config/servers/build/tests/trace-processing/fixtures/load.js","size":1168,"sha256":"0486a32ec095e71d3b75a17000861d6409b22e0d72fa646b96a1e5640fb2277d","contentType":"application/javascript; charset=utf-8"},{"id":"b6edc5f1-9e1d-544c-a3eb-b973569a2e2a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6edc5f1-9e1d-544c-a3eb-b973569a2e2a/attachment.js","path":"config/servers/build/tests/trace-processing/parse.test.js","size":1485,"sha256":"ba2267211a40fbb93dde485790d43376f8f7b5d495189497a295949886e07d6b","contentType":"application/javascript; charset=utf-8"},{"id":"b31f87a0-4709-5aea-a726-ae4ee626ccc6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b31f87a0-4709-5aea-a726-ae4ee626ccc6/attachment.js","path":"config/servers/build/tests/utils.js","size":6020,"sha256":"8e63ba9a8318e5dffc9dcb9b0795ebea6bdd9d8f43f5422c49f2528f75f7020b","contentType":"application/javascript; charset=utf-8"},{"id":"34a4a47f-f846-56e6-8745-a962b7be2195","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34a4a47f-f846-56e6-8745-a962b7be2195/attachment.tsbuildinfo","path":"config/servers/build/tsconfig.tsbuildinfo","size":220790,"sha256":"1e0b56e73e99f340eef0e7026c08b5e61cc60fc197a0700ca752551a0e3d0061","contentType":"text/plain; charset=utf-8"},{"id":"8c94f249-bf59-5653-ac9b-f4cf833bd49d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8c94f249-bf59-5653-ac9b-f4cf833bd49d/attachment.md","path":"config/servers/docs/debugging-android.md","size":1318,"sha256":"656563f9713c5e146806021595ac605d944405f95ea2ef22b6af979a5d132509","contentType":"text/markdown; charset=utf-8"},{"id":"4829c11f-98aa-5196-8dd1-0e1e907c8e51","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4829c11f-98aa-5196-8dd1-0e1e907c8e51/attachment.md","path":"config/servers/docs/design-principles.md","size":686,"sha256":"5b96a0eda59d7919ec88049f6d3f2eb0ea5e3313b486701ca37b71ca2387456c","contentType":"text/markdown; charset=utf-8"},{"id":"722cc567-4b8e-5f15-847e-b8cd5e93860b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/722cc567-4b8e-5f15-847e-b8cd5e93860b/attachment.md","path":"config/servers/docs/tool-reference.md","size":11611,"sha256":"1a97f5a29b7092c7d4129d0f90607a774c4568890b3f40c1c479ebd643c6cec5","contentType":"text/markdown; charset=utf-8"},{"id":"fda67ed0-45b4-52ff-a728-7b666d80e42e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fda67ed0-45b4-52ff-a728-7b666d80e42e/attachment.md","path":"config/servers/docs/troubleshooting.md","size":2191,"sha256":"c66832f43bf6d2c4910c272c5cb5b86c00d1a780495a3f4f7b4cd82132f591d4","contentType":"text/markdown; charset=utf-8"},{"id":"615ae39d-79a0-5295-940f-724431d39f09","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/615ae39d-79a0-5295-940f-724431d39f09/attachment.mjs","path":"config/servers/eslint.config.mjs","size":3378,"sha256":"b50074b4cc55d553849f62dca0d2065afe413fdf9587ef64a8deba3840b46478","contentType":"text/javascript"},{"id":"d200de2a-3c06-5cbc-8cbf-ff7df0ea8fe9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d200de2a-3c06-5cbc-8cbf-ff7df0ea8fe9/attachment.json","path":"config/servers/gemini-extension.json","size":182,"sha256":"0e9104e5745a55e9236cdfb49a8d5b243902e89bad823159b8753c8b009f4fd9","contentType":"application/json; charset=utf-8"},{"id":"253e9314-e19f-5217-995c-8c14763dd442","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/253e9314-e19f-5217-995c-8c14763dd442/attachment.ps1","path":"config/servers/install_mcp.ps1","size":1928,"sha256":"a9d4172bb8bff67b3827ede40769fc67d8a4e758e3adbfa4ae60c6c4d72367b9","contentType":"text/plain; charset=utf-8"},{"id":"4479233b-d3a1-5eba-ab55-063d2573f206","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4479233b-d3a1-5eba-ab55-063d2573f206/attachment.json","path":"config/servers/package.json","size":3572,"sha256":"2f0c30f0eabf1c84a9ca0313c25a448fd36520cb19b6b4b6adb5848508909e00","contentType":"application/json; charset=utf-8"},{"id":"7eddfede-d636-54d5-86ed-e5c00e62fe42","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7eddfede-d636-54d5-86ed-e5c00e62fe42/attachment.cjs","path":"config/servers/puppeteer.config.cjs","size":309,"sha256":"b7a6373d8eab96fa14f9ea273ef3d059186aa41296cb7859af0525d740dc0b8e","contentType":"text/plain; charset=utf-8"},{"id":"3398cff6-8815-59c3-86a3-39935abc8cc5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3398cff6-8815-59c3-86a3-39935abc8cc5/attachment.json","path":"config/servers/release-please-config.json","size":1037,"sha256":"3f683e73f5c322fc02b6f446c2e52b0678957a6aaea99fc8844052972bfc4469","contentType":"application/json; charset=utf-8"},{"id":"f6062187-a9f0-5b01-a379-e3a2c3332537","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f6062187-a9f0-5b01-a379-e3a2c3332537/attachment.mjs","path":"config/servers/rollup.config.mjs","size":5743,"sha256":"026e980e85b09c4ef166fffac1f927cb4ca916b82c8bd38857af4e6954cdc057","contentType":"text/javascript"},{"id":"f5b2161f-547f-5486-b835-b83466b46b19","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f5b2161f-547f-5486-b835-b83466b46b19/attachment.js","path":"config/servers/scripts/eslint_rules/check-license-rule.js","size":1893,"sha256":"4b2876d5fb821634952fbc6d152cc6276e095683fe373aa65ceb6a4ecfa990c8","contentType":"application/javascript; charset=utf-8"},{"id":"cc4921a7-3a83-5a42-a774-b19b235893a3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cc4921a7-3a83-5a42-a774-b19b235893a3/attachment.js","path":"config/servers/scripts/eslint_rules/local-plugin.js","size":207,"sha256":"01282663e6960ab03d6e7bf9782c7f533d654588a55676623ed11c163efa822c","contentType":"application/javascript; charset=utf-8"},{"id":"16f59996-905d-5138-bc20-e39e8ef20a40","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/16f59996-905d-5138-bc20-e39e8ef20a40/attachment.ts","path":"config/servers/scripts/generate-docs.ts","size":13495,"sha256":"fca45b8e7e493c85053d4fb6ef21e863097125e7237f2465266fe6705fbd0504","contentType":"text/typescript; charset=utf-8"},{"id":"4fb36825-af89-537d-8d44-3c9ebcfc8c3f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4fb36825-af89-537d-8d44-3c9ebcfc8c3f/attachment.ts","path":"config/servers/scripts/post-build.ts","size":3097,"sha256":"b60e0024ec49a780c02217481e4997797cf8d973406f4d4d93580b5bb597e6fb","contentType":"text/typescript; charset=utf-8"},{"id":"0eac00d1-29e1-54e0-a7bf-d4e5f133b1d9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0eac00d1-29e1-54e0-a7bf-d4e5f133b1d9/attachment.ts","path":"config/servers/scripts/prepare.ts","size":960,"sha256":"03d0dc1a3397570e24ce68084eaf827d3a615c2da78cdef8e3c98d037082f7c7","contentType":"text/typescript; charset=utf-8"},{"id":"24965e6b-c261-56ba-bcb3-49283aa8c1c1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/24965e6b-c261-56ba-bcb3-49283aa8c1c1/attachment.ts","path":"config/servers/scripts/sed.ts","size":653,"sha256":"b5a61821a9b6913f11e37b7acd5a023ba20c37a0cb10a09d804fddd558c9321a","contentType":"text/typescript; charset=utf-8"},{"id":"c70b6ca8-05eb-55c7-a7a5-ebb0e9010e5e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c70b6ca8-05eb-55c7-a7a5-ebb0e9010e5e/attachment.json","path":"config/servers/scripts/tsconfig.json","size":608,"sha256":"db97213f897c6999eb33c774814b7ac36adc421773fdb735af23c8fdc2c79188","contentType":"application/json; charset=utf-8"},{"id":"57dbae6a-6b4d-5a07-8671-32a3a6d98262","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/57dbae6a-6b4d-5a07-8671-32a3a6d98262/attachment.ts","path":"config/servers/scripts/verify-server-json-version.ts","size":914,"sha256":"e63b0ccf6d9509240993c17486298c6dbd4f6ce497450ed84963835db6f286c1","contentType":"text/typescript; charset=utf-8"},{"id":"ddbbbcd4-249f-5785-b93c-265999f21e29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ddbbbcd4-249f-5785-b93c-265999f21e29/attachment.json","path":"config/servers/server.json","size":650,"sha256":"f797b43d5486b8cfd735f2d560a1abdff6ed33b01c915450fc7c4a3fba6cecf1","contentType":"application/json; charset=utf-8"},{"id":"661f94e4-0686-5a28-ac28-15e966c4a80a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/661f94e4-0686-5a28-ac28-15e966c4a80a/attachment.ts","path":"config/servers/src/DevToolsConnectionAdapter.ts","size":3425,"sha256":"074b39b68d5d30cce228c92be6ac812ff9a1e121d26b1a8a6d15b47c466b55fb","contentType":"text/typescript; charset=utf-8"},{"id":"3a4eaeed-6ab8-59dd-8fc7-fcb0dadfe46a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3a4eaeed-6ab8-59dd-8fc7-fcb0dadfe46a/attachment.ts","path":"config/servers/src/DevtoolsUtils.ts","size":7610,"sha256":"4007cfddfbfce6216fa9e2ac1c218774cd5669717a5f0eeb191abf1b1a261237","contentType":"text/typescript; charset=utf-8"},{"id":"cc5a3aa8-0b17-5711-b798-26af40f0aec9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cc5a3aa8-0b17-5711-b798-26af40f0aec9/attachment.ts","path":"config/servers/src/McpContext.ts","size":19485,"sha256":"3c0a382f82f805f4f84d421b363ee23694a57b26bb87be8cf2078a5601b1dafd","contentType":"text/typescript; charset=utf-8"},{"id":"93bae3fc-f71e-5812-b5d9-19020afa4e98","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/93bae3fc-f71e-5812-b5d9-19020afa4e98/attachment.ts","path":"config/servers/src/McpResponse.ts","size":16686,"sha256":"ba2bbf978f829355c5704e865bf4cdc90eb114eb3b32f01fe5f5ada969b1fabe","contentType":"text/typescript; charset=utf-8"},{"id":"bab02709-1667-5e41-a53f-2978a76fcd20","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bab02709-1667-5e41-a53f-2978a76fcd20/attachment.ts","path":"config/servers/src/Mutex.ts","size":839,"sha256":"f67bf060995fc092ea960bc5d58fe657c7c644426ea70bf7a250abb87adf7bc5","contentType":"text/typescript; charset=utf-8"},{"id":"5f91d51c-330f-5c96-bd8b-750c40c77420","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5f91d51c-330f-5c96-bd8b-750c40c77420/attachment.ts","path":"config/servers/src/PageCollector.ts","size":10247,"sha256":"c3fa5c787e605acd62cfafbee1e158d36c8a398f2a1b9c16f9ee9d009f0b350d","contentType":"text/typescript; charset=utf-8"},{"id":"c520b9e0-38b3-59b9-ad05-869171948bc3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c520b9e0-38b3-59b9-ad05-869171948bc3/attachment.ts","path":"config/servers/src/WaitForHelper.ts","size":4479,"sha256":"10f7c484c99034b60781dc60778c0654ffefb0730c4eb9fd1831077feb68a0e7","contentType":"text/typescript; charset=utf-8"},{"id":"b6ee5b39-4b58-5a59-a4a0-46ed3dcf6828","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6ee5b39-4b58-5a59-a4a0-46ed3dcf6828/attachment.ts","path":"config/servers/src/browser.ts","size":6545,"sha256":"0eb2c664f23049968c51244142bb766bc364fea70ca28b10bde6c0bec3f1dcdc","contentType":"text/typescript; charset=utf-8"},{"id":"d6d7a01e-c435-5ce6-a8f0-f5bdc25aa4c7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d6d7a01e-c435-5ce6-a8f0-f5bdc25aa4c7/attachment.ts","path":"config/servers/src/cli.ts","size":8382,"sha256":"d092c9b2552126584eade667fa841f888864323fe87871cdb9f3e987de8efcf9","contentType":"text/typescript; charset=utf-8"},{"id":"75bdd45b-4d2f-5285-b825-a2e4dd7a49a8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/75bdd45b-4d2f-5285-b825-a2e4dd7a49a8/attachment.ts","path":"config/servers/src/devtools.d.ts","size":214,"sha256":"6f360965140319298fe4db318af582b6bc88ab7f0efcafd374e7c9116b560b0c","contentType":"text/typescript; charset=utf-8"},{"id":"ea7a110a-14f0-5bdd-ad52-4ec60c82a4e8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea7a110a-14f0-5bdd-ad52-4ec60c82a4e8/attachment.ts","path":"config/servers/src/formatters/consoleFormatter.ts","size":4825,"sha256":"038cfd17fca94efd4e56991e1230d7e88fae60234f6763cd58abc9cb3aa79114","contentType":"text/typescript; charset=utf-8"},{"id":"43e27509-dfe2-5ec8-927f-f7bf44917880","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/43e27509-dfe2-5ec8-927f-f7bf44917880/attachment.ts","path":"config/servers/src/formatters/networkFormatter.ts","size":2642,"sha256":"0e778ef1009b4fd68b2d4235679427fabe70cb2a900a70f337306d8fd4b5c12c","contentType":"text/typescript; charset=utf-8"},{"id":"f95914ed-441c-5260-8be1-fe737e099ef8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f95914ed-441c-5260-8be1-fe737e099ef8/attachment.ts","path":"config/servers/src/formatters/snapshotFormatter.ts","size":2434,"sha256":"b483f7e14e854b9b132e646bdaa7f55e07588d6499a6394bc6b8c37396eb6ff2","contentType":"text/typescript; charset=utf-8"},{"id":"795024ac-e8b1-5821-8e4b-dc8f81e68343","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/795024ac-e8b1-5821-8e4b-dc8f81e68343/attachment.ts","path":"config/servers/src/index.ts","size":856,"sha256":"ba9f11dbd436998ed7deac3e4a09d2278c777968f3e6aa020536d7e7b53e8136","contentType":"text/typescript; charset=utf-8"},{"id":"9d80e0c8-338a-567f-a000-09d5d2e96c75","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9d80e0c8-338a-567f-a000-09d5d2e96c75/attachment.ts","path":"config/servers/src/issue-descriptions.ts","size":1295,"sha256":"45ed59adef8a6481942b05c6654f99b24af92e905e281670ee4423268dce2e4b","contentType":"text/typescript; charset=utf-8"},{"id":"644aa59c-4590-586e-be4b-a1cde60b0d31","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/644aa59c-4590-586e-be4b-a1cde60b0d31/attachment.ts","path":"config/servers/src/logger.ts","size":877,"sha256":"1dd18bdff70c86a923b08adf3fa5a8650eb27408de4aad511095662e9886b24e","contentType":"text/typescript; charset=utf-8"},{"id":"84f5631b-b078-521b-9425-08ce0079be2c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/84f5631b-b078-521b-9425-08ce0079be2c/attachment.ts","path":"config/servers/src/main.ts","size":5185,"sha256":"20a351488ac3e528d2d33c68d2916051e65a3c270a63f0a1a875570013329f46","contentType":"text/typescript; charset=utf-8"},{"id":"d0b7e99a-9f23-5451-9f02-c23480a60624","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d0b7e99a-9f23-5451-9f02-c23480a60624/attachment.ts","path":"config/servers/src/polyfill.ts","size":180,"sha256":"6c57947a3d171e374794748da80be23dbcf9ee504edf6aef6d5ce2de1ee4e3cc","contentType":"text/typescript; charset=utf-8"},{"id":"2962fca9-044d-5fb2-9fe4-58b09544e414","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2962fca9-044d-5fb2-9fe4-58b09544e414/attachment.ts","path":"config/servers/src/third_party/devtools.ts","size":648,"sha256":"0b732be17fa7956ebcd9173b0bf22bde87d64bae2e9624ff983de49627d140b1","contentType":"text/typescript; charset=utf-8"},{"id":"fc0c70a1-2c38-5911-acd5-9367273d66fa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fc0c70a1-2c38-5911-acd5-9367273d66fa/attachment.ts","path":"config/servers/src/third_party/index.ts","size":1196,"sha256":"b5ad1ba397f9e7483284270b112146e676242dd5e8f3a3ef21995442bf7caf54","contentType":"text/typescript; charset=utf-8"},{"id":"22b67060-4ef8-515a-8a98-bf25180f44ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/22b67060-4ef8-515a-8a98-bf25180f44ca/attachment.ts","path":"config/servers/src/tools/ToolDefinition.ts","size":4068,"sha256":"df83574cef7ea6512cc3403bb61ada7a8b15ad4de18b08113e2124b3daea97f2","contentType":"text/typescript; charset=utf-8"},{"id":"1bcbb442-71d8-5625-b4b6-d9e4dbb5ef49","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1bcbb442-71d8-5625-b4b6-d9e4dbb5ef49/attachment.ts","path":"config/servers/src/tools/categories.ts","size":564,"sha256":"2f5ef77b3ad27e23a2b3017ad7a15ac262b23bbe326da52be7ca90f7eff4b837","contentType":"text/typescript; charset=utf-8"},{"id":"173fa353-1c2c-5c40-b0f0-e0a75adaae78","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/173fa353-1c2c-5c40-b0f0-e0a75adaae78/attachment.ts","path":"config/servers/src/tools/console.ts","size":2641,"sha256":"fac95d1f871671b66b014f23dff73cd3fd97b2fe2bc6513f8981e47bf332f4ab","contentType":"text/typescript; charset=utf-8"},{"id":"9b00617e-199d-5868-a907-b1c1c3d4c1ba","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9b00617e-199d-5868-a907-b1c1c3d4c1ba/attachment.ts","path":"config/servers/src/tools/emulation.ts","size":2948,"sha256":"b08ed4079dbe261ea8c99aa96ad8a30139d00d3ddb84d0eab12cca4b6d438a0c","contentType":"text/typescript; charset=utf-8"},{"id":"3a2c719e-6ed2-56f9-9ab8-76ff85eda0ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3a2c719e-6ed2-56f9-9ab8-76ff85eda0ca/attachment.ts","path":"config/servers/src/tools/input.ts","size":9402,"sha256":"5453fcd747b0f07620a02cda062ff0785f0b28cc297923fb6da0682f7f4035b1","contentType":"text/typescript; charset=utf-8"},{"id":"f252fd8a-9e47-5ee6-abc8-aca964faa9cd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f252fd8a-9e47-5ee6-abc8-aca964faa9cd/attachment.ts","path":"config/servers/src/tools/network.ts","size":3410,"sha256":"f03eca09cc63239cc6efdc422335ae9a422157c2942962c89dc60e0a66c04152","contentType":"text/typescript; charset=utf-8"},{"id":"facb5070-24be-5873-8cb8-d247e0a8239c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/facb5070-24be-5873-8cb8-d247e0a8239c/attachment.ts","path":"config/servers/src/tools/pages.ts","size":7562,"sha256":"ddc4e298d79899a036ae648f09ffee5b677a7f86bc2bd07a05ec2baaaf459d08","contentType":"text/typescript; charset=utf-8"},{"id":"fab234f6-f6c7-5dd2-9ede-724308931442","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fab234f6-f6c7-5dd2-9ede-724308931442/attachment.ts","path":"config/servers/src/tools/performance.ts","size":6247,"sha256":"50b5f6cde122205693284de875b9d26308c01ee62c5581cb28bfca834af69602","contentType":"text/typescript; charset=utf-8"},{"id":"7f1d2f91-cbce-5d74-bd9e-934b4030f9d5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7f1d2f91-cbce-5d74-bd9e-934b4030f9d5/attachment.ts","path":"config/servers/src/tools/screenshot.ts","size":3359,"sha256":"4a797e76fda99602bffb980c57bc52d7b71cfc0845cf63bd032f45762223c28f","contentType":"text/typescript; charset=utf-8"},{"id":"9c1b6845-f1f0-541e-8233-d9fd6e001fd6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9c1b6845-f1f0-541e-8233-d9fd6e001fd6/attachment.ts","path":"config/servers/src/tools/script.ts","size":2679,"sha256":"c7659792ecda1131351807d76cfa22017c27fdf5a1eb71699fd903fe9e6df1da","contentType":"text/typescript; charset=utf-8"},{"id":"e9ce1cd1-9737-5661-b664-390193087169","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e9ce1cd1-9737-5661-b664-390193087169/attachment.ts","path":"config/servers/src/tools/snapshot.ts","size":1995,"sha256":"fed96aa323a6481397ad6c14658c6a075194b15f7a75e8e7942ba693671915d8","contentType":"text/typescript; charset=utf-8"},{"id":"395c358e-798e-5d35-a735-59d55451faa7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/395c358e-798e-5d35-a735-59d55451faa7/attachment.ts","path":"config/servers/src/tools/tools.ts","size":1004,"sha256":"54b3a73924525f31a337cd4b167f497ac046dd53220e1d11ea83e57c029344cb","contentType":"text/typescript; charset=utf-8"},{"id":"911578e0-c43c-5a2a-87e0-85d1147242dc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/911578e0-c43c-5a2a-87e0-85d1147242dc/attachment.ts","path":"config/servers/src/trace-processing/parse.ts","size":3590,"sha256":"f602f1d3422db4ed840738f11b56aba3cceb5b4ddff552eed24717338e49ae2d","contentType":"text/typescript; charset=utf-8"},{"id":"a857445d-6f0a-5f1b-ba4b-31e3513b4a71","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a857445d-6f0a-5f1b-ba4b-31e3513b4a71/attachment.ts","path":"config/servers/src/utils/keyboard.ts","size":3825,"sha256":"7ffb098bbf548a7f2e7f4f28e1f3c408d5f518ee1db0663b3f40588821d224cd","contentType":"text/typescript; charset=utf-8"},{"id":"1d8207f9-0749-5cbf-ab94-0969db8cdb53","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1d8207f9-0749-5cbf-ab94-0969db8cdb53/attachment.ts","path":"config/servers/src/utils/pagination.ts","size":1920,"sha256":"31b93d36c4c45603b33655e02a5ba86200eb9ed6d5e11e091d34d52dc693ff91","contentType":"text/typescript; charset=utf-8"},{"id":"684e5b2e-c15e-5e0d-b180-d366da48429b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/684e5b2e-c15e-5e0d-b180-d366da48429b/attachment.ts","path":"config/servers/src/utils/types.ts","size":169,"sha256":"2ab4ffda18a12c041a902770e417675eae2e505ca18eb295ca58d67fd10f4b36","contentType":"text/typescript; charset=utf-8"},{"id":"1b2ceed4-c347-5308-9049-6ba713f7a4e3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1b2ceed4-c347-5308-9049-6ba713f7a4e3/attachment.ts","path":"config/servers/tests/DevtoolsUtils.test.ts","size":7892,"sha256":"4fe89aaeda85bec39968586bb33eed6649e853232bfb81abb4c98311fb0552ac","contentType":"text/typescript; charset=utf-8"},{"id":"31f7a044-55a5-5de9-8021-ce7acb2a235e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/31f7a044-55a5-5de9-8021-ce7acb2a235e/attachment.ts","path":"config/servers/tests/McpContext.test.ts","size":3462,"sha256":"ddc9a14106d273b785eb68a569de9793c2a5789193512517739891b82b894424","contentType":"text/typescript; charset=utf-8"},{"id":"584739ef-db84-5d68-8522-7cf508daff06","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/584739ef-db84-5d68-8522-7cf508daff06/attachment.snapshot","path":"config/servers/tests/McpResponse.test.js.snapshot","size":4696,"sha256":"d54303042bb49fbb0aab2942df912011bd8af12c96934c3d1f46feb80e0c2b40","contentType":"text/plain; charset=utf-8"},{"id":"54b2f702-6f76-5a36-b0f6-d994b0a65e80","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/54b2f702-6f76-5a36-b0f6-d994b0a65e80/attachment.ts","path":"config/servers/tests/McpResponse.test.ts","size":17485,"sha256":"656c776779b7383743447fd73bcf99f8f1cdcc0dbfac39fa1a41900129def821","contentType":"text/typescript; charset=utf-8"},{"id":"0cbf21de-1a9b-5327-8aff-ca028d0c02ed","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0cbf21de-1a9b-5327-8aff-ca028d0c02ed/attachment.ts","path":"config/servers/tests/PageCollector.test.ts","size":11503,"sha256":"0585b33d72b4d1a65328f819c4bac94d133e56dcf354747393c99dfe63dabdb6","contentType":"text/typescript; charset=utf-8"},{"id":"e22816f9-078c-5d1b-9365-7c416e5e4090","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e22816f9-078c-5d1b-9365-7c416e5e4090/attachment.ts","path":"config/servers/tests/browser.test.ts","size":2769,"sha256":"73ce8a4f2bfbd4cb3641ff28ef3e63b459b7ff02aafe509deef3bba31a90fed5","contentType":"text/typescript; charset=utf-8"},{"id":"109928ef-66cd-52ae-9736-1178dff00523","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/109928ef-66cd-52ae-9736-1178dff00523/attachment.ts","path":"config/servers/tests/cli.test.ts","size":5824,"sha256":"0b6e7621d61cf9ccc982e128805cd5a88a4883d46f011ddc23f58d0224a01796","contentType":"text/typescript; charset=utf-8"},{"id":"217288f8-7aa2-5e35-8f7d-94255134cd75","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/217288f8-7aa2-5e35-8f7d-94255134cd75/attachment.snapshot","path":"config/servers/tests/formatters/consoleFormatter.test.js.snapshot","size":1383,"sha256":"c15fd4d0be27e4d8b991cf75890f30f47565ae891b109d539768895cad3e3432","contentType":"text/plain; charset=utf-8"},{"id":"d5f4ac78-c38b-53dd-9a79-a3262d506c56","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d5f4ac78-c38b-53dd-9a79-a3262d506c56/attachment.ts","path":"config/servers/tests/formatters/consoleFormatter.test.ts","size":3982,"sha256":"b3d5b8744f7dd23ffe800e97347e8d5773ad89c25df93e195ea61fcca34cfb5d","contentType":"text/typescript; charset=utf-8"},{"id":"104913c0-ff26-5c9f-a019-4c7d7a72b49d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/104913c0-ff26-5c9f-a019-4c7d7a72b49d/attachment.ts","path":"config/servers/tests/formatters/networkFormatter.test.ts","size":7603,"sha256":"996ce2e9c9ea5db7437a4ace788c80e5bbb0ff9dfda9be973e8a56ee2540e64a","contentType":"text/typescript; charset=utf-8"},{"id":"c1678e52-d392-52be-b38a-382a6a873d5e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c1678e52-d392-52be-b38a-382a6a873d5e/attachment.snapshot","path":"config/servers/tests/formatters/snapshotFormatter.test.js.snapshot","size":746,"sha256":"f0a231709702dac127dadd9341217ed5610e883772f3893867c0e65c4b1da25b","contentType":"text/plain; charset=utf-8"},{"id":"c9522f21-683c-5c52-a26c-8648b5a8ad81","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c9522f21-683c-5c52-a26c-8648b5a8ad81/attachment.ts","path":"config/servers/tests/formatters/snapshotFormatter.test.ts","size":6125,"sha256":"4e998d992faea655f3fc63f0e5ec00768504f74b20dd7b67a9a12233aae976ef","contentType":"text/typescript; charset=utf-8"},{"id":"79aac60f-a823-5609-a76e-a92a37ca4b5b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/79aac60f-a823-5609-a76e-a92a37ca4b5b/attachment.ts","path":"config/servers/tests/index.test.ts","size":2629,"sha256":"6d5d2b8ea8740e1e1cb2069dee089a80732fdb4c04eca5b77773e941390647ff","contentType":"text/typescript; charset=utf-8"},{"id":"8ae90dab-3bd8-51a6-8d79-75bba111c4a5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8ae90dab-3bd8-51a6-8d79-75bba111c4a5/attachment.ts","path":"config/servers/tests/server.ts","size":2773,"sha256":"6afc75f293c46355ad1c67c67387b1dd12be1c2f4bdab6937bb5b2958216d642","contentType":"text/typescript; charset=utf-8"},{"id":"32b69ecd-200b-5f76-b1cd-d70bb4a38416","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32b69ecd-200b-5f76-b1cd-d70bb4a38416/attachment.ts","path":"config/servers/tests/setup.ts","size":943,"sha256":"4d65a95e742c44454866ecfdc756323408b0fdaf873a5d3156e0cb0d9ada8ba0","contentType":"text/typescript; charset=utf-8"},{"id":"19f8eab2-b65e-5699-860e-e87e98791c22","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/19f8eab2-b65e-5699-860e-e87e98791c22/attachment.ts","path":"config/servers/tests/snapshot.ts","size":443,"sha256":"eeea30df68a87cdac30df131f42c7899848186a04bc9f8f4b18f2b83b0aaa0b0","contentType":"text/typescript; charset=utf-8"},{"id":"07f1af52-ece8-5568-9805-3ed93b25dee5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/07f1af52-ece8-5568-9805-3ed93b25dee5/attachment.snapshot","path":"config/servers/tests/tools/console.test.js.snapshot","size":1944,"sha256":"b81e3a8c06a6561ed0c9fbef515fa0939ca1d6a1bec1b1571c0b0751b90211ed","contentType":"text/plain; charset=utf-8"},{"id":"aa562899-678f-5ef8-9490-d1ebf52c5d32","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aa562899-678f-5ef8-9490-d1ebf52c5d32/attachment.ts","path":"config/servers/tests/tools/console.test.ts","size":8487,"sha256":"277be4293dd5c8d1be044d67d3bdea0c5470022ac0f4c7607c9305798a8e10fb","contentType":"text/typescript; charset=utf-8"},{"id":"08fac25a-8ddd-5dde-b140-885a20ffb967","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/08fac25a-8ddd-5dde-b140-885a20ffb967/attachment.ts","path":"config/servers/tests/tools/emulation.test.ts","size":6137,"sha256":"0f718980c2bfd22ca81b8096bb689021de1489ed8f4c57230f6cdaccf1fada67","contentType":"text/typescript; charset=utf-8"},{"id":"5c413364-2b13-5c87-a693-197cb0d64d0a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5c413364-2b13-5c87-a693-197cb0d64d0a/attachment.ts","path":"config/servers/tests/tools/input.test.ts","size":15380,"sha256":"5e2eb7cf4075202ce60d7fae7edd35c0345c36632f3b8108b7493261706d9ae1","contentType":"text/typescript; charset=utf-8"},{"id":"1459ab84-1528-549c-aa1c-e7bedec837b4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1459ab84-1528-549c-aa1c-e7bedec837b4/attachment.snapshot","path":"config/servers/tests/tools/network.test.js.snapshot","size":1872,"sha256":"8f68e4f8608ea9fae63fba5ea5a39106994342536d5dfe5e11ac5a85e5862a0a","contentType":"text/plain; charset=utf-8"},{"id":"e5d9b339-0254-5e55-b521-7195393ed4b5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e5d9b339-0254-5e55-b521-7195393ed4b5/attachment.ts","path":"config/servers/tests/tools/network.test.ts","size":5697,"sha256":"2bd922aabc3b7800d72ae9b724e3e7818d618d81fca37c3e3fa26c2f24b87474","contentType":"text/typescript; charset=utf-8"},{"id":"8320189b-5012-551c-af72-173c9be1eadf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8320189b-5012-551c-af72-173c9be1eadf/attachment.ts","path":"config/servers/tests/tools/pages.test.ts","size":10239,"sha256":"35471fe9c3eda7ac7df3961346204355eeb6318c99ba771691f75deff52cff3b","contentType":"text/typescript; charset=utf-8"},{"id":"1d18b74f-2768-5b1f-b87f-90462bdf13ba","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1d18b74f-2768-5b1f-b87f-90462bdf13ba/attachment.snapshot","path":"config/servers/tests/tools/performance.test.js.snapshot","size":10353,"sha256":"f2e7a3f93e5fdc1cf2d53efdbd67349257ea8597377fcfde95be852def42cb7e","contentType":"text/plain; charset=utf-8"},{"id":"fb653631-9097-5578-b6fd-e00daca03127","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb653631-9097-5578-b6fd-e00daca03127/attachment.ts","path":"config/servers/tests/tools/performance.test.ts","size":9726,"sha256":"5ed8779803d89d2bcc559a75dadcd0fe978f15f3213aa6c522e755b9851da7c9","contentType":"text/typescript; charset=utf-8"},{"id":"a254fe10-89d4-5715-a603-25cd6adf9bc0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a254fe10-89d4-5715-a603-25cd6adf9bc0/attachment.ts","path":"config/servers/tests/tools/screenshot.test.ts","size":8544,"sha256":"f7a0cbb35659c3b2581d110d9044e374e36be3d2e05af7fd0c1711f85e4f0a22","contentType":"text/typescript; charset=utf-8"},{"id":"5ea6b106-e5de-5bc6-abf6-ef96334bb8f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ea6b106-e5de-5bc6-abf6-ef96334bb8f0/attachment.ts","path":"config/servers/tests/tools/script.test.ts","size":5570,"sha256":"2045a7d7205bcb3b2b145bd0d756815f7c9b70a5d41e97c0b4e561a18c872e7a","contentType":"text/typescript; charset=utf-8"},{"id":"4eaef0c5-ecf8-542a-9d32-4b511376f231","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4eaef0c5-ecf8-542a-9d32-4b511376f231/attachment.ts","path":"config/servers/tests/tools/snapshot.test.ts","size":3250,"sha256":"44c5b578868d7b4a2ef5973d0d6a06ef654fdc35f3892eb0cdca61af83a80528","contentType":"text/typescript; charset=utf-8"},{"id":"6042f70a-e4b3-5c9a-83ca-ea94263239f3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6042f70a-e4b3-5c9a-83ca-ea94263239f3/attachment.gz","path":"config/servers/tests/trace-processing/fixtures/basic-trace.json.gz","size":845,"sha256":"bab5258499dfaa84e14beee411368a0058a640ce6a920f4e77b86190ab2f156b","contentType":"application/json"},{"id":"61438567-0670-5e0d-bb8a-80e0c74af3b0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/61438567-0670-5e0d-bb8a-80e0c74af3b0/attachment.ts","path":"config/servers/tests/trace-processing/fixtures/load.ts","size":1229,"sha256":"d172155e19788be8977e79645a1cf2600b877e0949549cfcdd73db1288b9e34e","contentType":"text/typescript; charset=utf-8"},{"id":"12893acc-a3b9-588e-bdbb-548933872796","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/12893acc-a3b9-588e-bdbb-548933872796/attachment.gz","path":"config/servers/tests/trace-processing/fixtures/web-dev-with-commit.json.gz","size":1006156,"sha256":"c28408c3917a9943b0f4c2e4f0cacda21c7b26ece58913f0786f270dc2c7cde1","contentType":"application/json"},{"id":"d88697fb-5763-53dc-9963-a03a2efe5f8f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d88697fb-5763-53dc-9963-a03a2efe5f8f/attachment.snapshot","path":"config/servers/tests/trace-processing/parse.test.js.snapshot","size":8297,"sha256":"33a8be563aa8543293aa9a193171f58ba6fb7205d0795f3c2d8d074d6eae6076","contentType":"text/plain; charset=utf-8"},{"id":"66798c4d-4eb2-51a7-955e-4292485597bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/66798c4d-4eb2-51a7-955e-4292485597bd/attachment.ts","path":"config/servers/tests/trace-processing/parse.test.ts","size":1393,"sha256":"555e50d6b83be1c5175860b780972e2cc473f42c9474d4e919a7c56c4c7d967d","contentType":"text/typescript; charset=utf-8"},{"id":"875380f0-ddeb-5d0c-a95a-b53a97f77286","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/875380f0-ddeb-5d0c-a95a-b53a97f77286/attachment.ts","path":"config/servers/tests/utils.ts","size":6857,"sha256":"66b21375a7656c05556ca7206d6231e55c88201890d5ca41cf7b17754f304e4a","contentType":"text/typescript; charset=utf-8"},{"id":"ef3efa6e-59e3-564c-b125-47501d22d899","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ef3efa6e-59e3-564c-b125-47501d22d899/attachment.json","path":"config/servers/tsconfig.json","size":3878,"sha256":"ae9df4305cee81de18b6181b144480cd67cfc7f803f365b974707f4b11653678","contentType":"application/json; charset=utf-8"},{"id":"5393cdcf-ef84-5ffc-8993-058e163a5c63","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5393cdcf-ef84-5ffc-8993-058e163a5c63/attachment.json","path":"config/settings.json","size":4263,"sha256":"0a931d153c9bac78128bad6347cde34e66fca8e1933cbea7197062b066b7ea34","contentType":"application/json; charset=utf-8"},{"id":"fe8c03df-853c-596e-8b6a-e336eba39081","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fe8c03df-853c-596e-8b6a-e336eba39081/attachment.template","path":"config/settings.json.template","size":4266,"sha256":"b82eb37fcf4057e76a9f7e5a05ec5a821314dc259151cbaf0cbcc6fd2cd6c132","contentType":"text/plain; charset=utf-8"},{"id":"5ddeaa06-b1f8-5efa-af13-70759c0f698a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ddeaa06-b1f8-5efa-af13-70759c0f698a/attachment.py","path":"scripts/backup_env.py","size":9449,"sha256":"065bc7755f42d79f3cfd5033ee617bf3267460b780bb191cba5ef9c8e695db09","contentType":"text/x-python; charset=utf-8"},{"id":"35ce7947-463e-51ce-a7b7-b418c78af069","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/35ce7947-463e-51ce-a7b7-b418c78af069/attachment.py","path":"scripts/restore_env.py","size":10241,"sha256":"4c0a6c45aa21d1806f9a0e772a2deee4ff68197fe7d18fcc2e9ab679438e0a2e","contentType":"text/x-python; charset=utf-8"},{"id":"c38800f2-d1a7-5e24-b124-afea22fbaff0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c38800f2-d1a7-5e24-b124-afea22fbaff0/attachment.py","path":"scripts/sync_env.py","size":9288,"sha256":"85b85ba39798821f2cdbaaad7742ce79d2f7fd5af2ad8f54057fef716095dfa6","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"d8b30c1820dfdc45d1207eadc314e0883835e7cb28f4bceac870ebc92e3d1072","attachment_count":386,"text_attachments":324,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":62,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/env-setup/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"clean-skills-v1","description":"Claude Code 环境一键同步工具。从 GitHub 仓库同步所有配置到本地:output-styles 风格、CLAUDE.md 全局提示词、MCP 服务器配置、Agent 配置、Plugin 配置。适用于多设备统一环境、换电脑恢复、团队共享配置等场景。当用户需要从 GitHub 仓库同步 Claude Code/OpenClaw 环境配置时使用此 skill。"}},"renderedAt":1782986419269}

Claude Code & OpenClaw 环境一键同步工具 从 GitHub 仓库一键同步所有配置到本地 Claude Code 和 OpenClaw 环境。 功能概述 本 skill 提供 一键同步 功能,将配置从 GitHub 仓库同步到本地: - - 同步所有配置到本地 同步内容 | 组件 | 来源 | 目标 | 说明 | |------|------|------|------| | Output Styles | | | Claude Code 对话风格 | | CLAUDE.md | | | 全局提示词 | | MCP Config | | | MCP 服务器(合并) | | Agent Configs | | | OpenClaw Agent 配置 | | MCP Servers | | 集成到 | MCP 服务器独立配置 | | Plugins | | | OpenClaw 插件配置 | GitHub 仓库结构 配置目录说明 agents/ - Agent 配置 用于存放 OpenClaw Agent 的配置: 同步目标: mcp/ - MCP 服务器配置 用于存放 MCP 服务器的独立配置: 同步目标: 集成到 的 mcpServers plugins/ - 插件配置 用于存放 OpenClaw 插件配置: 同步目标: 使用方法 一、初始化 GitHub 仓…