@ait-co/devtools 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["isObject","isObject","jsonResult"],"sources":["../../src/mcp/ait-chii-source.ts","../../src/mcp/chii-connection.ts","../../src/mcp/chii-relay.ts","../../src/mcp/deeplink.ts","../../src/mcp/tools.ts","../../src/mcp/tunnel.ts","../../src/mcp/debug-server.ts","../../src/mcp/ait-http-source.ts","../../src/mcp/server.ts","../../src/mcp/cli.ts"],"sourcesContent":["/**\n * Debug-mode `AitSource` — forwards `AIT.*` methods over the Chii channel.\n *\n * The AIT domain (`AIT.getSdkCallHistory` / `getMockState` /\n * `getOperationalEnvironment`) is non-standard CDP: the in-app side registers a\n * handler for these methods and answers them over the same Chii websocket the\n * CDP commands use. Building the AIT source on `ChiiCdpConnection.sendCommand`\n * means both domains share one transport (spec: \"the same MCP server forwards\n * both CDP and AIT domains\").\n *\n * The in-app `AIT.*` handler lives downstream in sdk-example. Here we build\n * the MCP-server-side forwarding + the injectable seam; tests inject a fake\n * `AitSource` returning canned responses, so this forwarding layer needs no\n * phone.\n *\n * Node-only (wraps the relay websocket connection).\n */\n\nimport type {\n AitMethodMap,\n AitMethodName,\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\n\n/** The slice of `ChiiCdpConnection` this source needs (keeps it testable). */\nexport interface AitCommandSender {\n sendCommand(method: string, params?: Record<string, unknown>): Promise<unknown>;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Narrows an `AIT.getSdkCallHistory` response, tolerating a missing array. */\nfunction asSdkCallHistory(raw: unknown): AitSdkCallHistory {\n if (isObject(raw) && Array.isArray(raw.calls)) {\n return { calls: raw.calls as AitSdkCallHistory['calls'] };\n }\n return { calls: [] };\n}\n\n/** Narrows an `AIT.getMockState` response to an opaque record. */\nfunction asMockState(raw: unknown): AitMockState {\n return isObject(raw) ? raw : {};\n}\n\n/** Narrows an `AIT.getOperationalEnvironment` response. */\nfunction asOperationalEnvironment(raw: unknown): AitOperationalEnvironment {\n const environment =\n isObject(raw) && typeof raw.environment === 'string' ? raw.environment : 'unknown';\n const sdkVersion = isObject(raw) && typeof raw.sdkVersion === 'string' ? raw.sdkVersion : null;\n return { environment, sdkVersion };\n}\n\nexport class ChiiAitSource implements AitSource {\n constructor(private readonly sender: AitCommandSender) {}\n\n async get<M extends AitMethodName>(method: M): Promise<AitMethodMap[M]> {\n const raw = await this.sender.sendCommand(method);\n // The map's value type is resolved per-key below; the cast is the single\n // narrowing point (each branch returns the precise shape for `method`).\n switch (method) {\n case 'AIT.getSdkCallHistory':\n return asSdkCallHistory(raw) as AitMethodMap[M];\n case 'AIT.getMockState':\n return asMockState(raw) as AitMethodMap[M];\n case 'AIT.getOperationalEnvironment':\n return asOperationalEnvironment(raw) as AitMethodMap[M];\n default:\n throw new Error(`Unknown AIT method: ${String(method)}`);\n }\n }\n}\n","/**\n * Production `CdpConnection` backed by the local Chii relay.\n *\n * Topology (debug mode):\n * phone target.js --WS--> Chii relay :9100 <--WS-- this connection\n *\n * The phone connects to the relay as a `target`; this module connects as a\n * `client` (the role a CDP frontend would take) so CDP events the page emits\n * (`Runtime.consoleAPICalled`, `Network.*`) flow back here. We buffer recent\n * events in ring buffers the tool layer reads via `getBufferedEvents`.\n *\n * Node-only: imports `ws`. Never bundled into the browser/in-app entries.\n */\n\nimport { EventEmitter } from 'node:events';\nimport { WebSocket } from 'ws';\nimport type {\n CdpCommandMap,\n CdpCommandName,\n CdpConnection,\n CdpEventMap,\n CdpEventName,\n CdpTarget,\n} from './cdp-connection.js';\n\n/** Max events retained per domain ring buffer. */\nconst DEFAULT_BUFFER_SIZE = 500;\n\n/** A CDP message arriving over the relay websocket. */\ninterface CdpInboundMessage {\n id?: number;\n method?: string;\n params?: unknown;\n result?: unknown;\n error?: { message: string };\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction parseInbound(raw: string): CdpInboundMessage | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isObject(parsed)) return null;\n const message: CdpInboundMessage = {};\n if (typeof parsed.id === 'number') message.id = parsed.id;\n if (typeof parsed.method === 'string') message.method = parsed.method;\n if ('params' in parsed) message.params = parsed.params;\n if ('result' in parsed) message.result = parsed.result;\n if (isObject(parsed.error) && typeof parsed.error.message === 'string') {\n message.error = { message: parsed.error.message };\n }\n return message;\n}\n\nconst PHASE_1_EVENTS: readonly CdpEventName[] = [\n 'Runtime.consoleAPICalled',\n 'Network.requestWillBeSent',\n 'Network.responseReceived',\n];\n\nexport interface ChiiCdpConnectionOptions {\n /** Base URL of the local Chii relay HTTP/WS server, e.g. `http://127.0.0.1:9100`. */\n relayBaseUrl: string;\n /** Per-domain ring buffer size. */\n bufferSize?: number;\n}\n\n/**\n * Production CDP connection. Polls the relay for the first attached target,\n * opens a client websocket to it, enables Phase 1 domains, and buffers events.\n */\nexport class ChiiCdpConnection implements CdpConnection {\n private readonly relayBaseUrl: string;\n private readonly bufferSize: number;\n private readonly emitter = new EventEmitter();\n private readonly buffers = new Map<CdpEventName, unknown[]>();\n private readonly targets = new Map<string, CdpTarget>();\n\n private ws: WebSocket | null = null;\n private nextCommandId = 1;\n /** In-flight enableDomains() promise — concurrent callers share it. */\n private enablingPromise: Promise<void> | null = null;\n /** Pending request→response commands keyed by CDP message id. */\n private readonly pending = new Map<\n number,\n { resolve: (result: unknown) => void; reject: (err: Error) => void }\n >();\n\n constructor(options: ChiiCdpConnectionOptions) {\n this.relayBaseUrl = options.relayBaseUrl.replace(/\\/$/, '');\n this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE;\n for (const event of PHASE_1_EVENTS) this.buffers.set(event, []);\n // EventEmitter caps listeners at 10 by default; the tool layer may add\n // several short-lived subscriptions, so lift the cap.\n this.emitter.setMaxListeners(0);\n }\n\n /** Refresh the attached-target list from the relay's `GET /targets`. */\n async refreshTargets(): Promise<CdpTarget[]> {\n const res = await fetch(`${this.relayBaseUrl}/targets`);\n if (!res.ok) {\n throw new Error(`Chii relay /targets returned HTTP ${res.status} ${res.statusText}`);\n }\n const body: unknown = await res.json();\n const list = isObject(body) && Array.isArray(body.targets) ? body.targets : [];\n this.targets.clear();\n for (const item of list) {\n if (!isObject(item) || typeof item.id !== 'string') continue;\n this.targets.set(item.id, {\n id: item.id,\n title: typeof item.title === 'string' ? item.title : '',\n url: typeof item.url === 'string' ? item.url : '',\n });\n }\n return [...this.targets.values()];\n }\n\n listTargets(): CdpTarget[] {\n return [...this.targets.values()];\n }\n\n /**\n * Connect a client websocket to the first attached target and enable Phase 1\n * domains. Resolves once the socket is open and enable commands are sent.\n */\n async enableDomains(): Promise<void> {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) return;\n // If a connect attempt is already in-flight, await it rather than racing\n // to open a second websocket that would overwrite `this.ws` and leak the first.\n if (this.enablingPromise) return this.enablingPromise;\n this.enablingPromise = this._doEnableDomains().finally(() => {\n this.enablingPromise = null;\n });\n return this.enablingPromise;\n }\n\n private async _doEnableDomains(): Promise<void> {\n const targets = await this.refreshTargets();\n const target = targets[0];\n if (!target) {\n throw new Error('No mini-app page attached to the Chii relay yet.');\n }\n\n const wsBase = this.relayBaseUrl.replace(/^http/, 'ws');\n const clientId = `devtools-mcp-${Date.now()}`;\n const ws = new WebSocket(\n `${wsBase}/client/${clientId}?target=${encodeURIComponent(target.id)}`,\n );\n this.ws = ws;\n\n await new Promise<void>((resolve, reject) => {\n ws.once('open', () => resolve());\n ws.once('error', (err: Error) => reject(err));\n });\n\n ws.on('message', (data: WebSocket.RawData) => this.handleMessage(data.toString()));\n\n this.sendFireAndForget('Runtime.enable');\n this.sendFireAndForget('Network.enable');\n // DOM/Page domains back the Phase 2 command tools; Chii answers their\n // request→response commands once enabled.\n this.sendFireAndForget('DOM.enable');\n this.sendFireAndForget('Page.enable');\n }\n\n /** Fire-and-forget CDP message (used for `*.enable`, no result awaited). */\n private sendFireAndForget(method: string, params: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n const id = this.nextCommandId++;\n this.ws.send(JSON.stringify({ id, method, params }));\n }\n\n /**\n * Issue a CDP command and resolve with its result (Phase 2). Rejects on a CDP\n * error frame or when no websocket is open (no page attached yet).\n */\n send<M extends CdpCommandName>(\n method: M,\n params?: CdpCommandMap[M]['params'],\n ): Promise<CdpCommandMap[M]['result']> {\n return this.sendCommand(method, params ?? {}) as Promise<CdpCommandMap[M]['result']>;\n }\n\n /**\n * Issue an arbitrary request→response command over the relay and resolve with\n * its raw result. Both the typed CDP {@link send} and the AIT domain (Phase 3\n * `AIT.*` methods, forwarded over the same Chii channel) build on this.\n */\n sendCommand(method: string, params: Record<string, unknown> = {}): Promise<unknown> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return Promise.reject(\n new Error('No mini-app page attached to the Chii relay yet. Call enableDomains() first.'),\n );\n }\n const id = this.nextCommandId++;\n const ws = this.ws;\n return new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n ws.send(JSON.stringify({ id, method, params }));\n });\n }\n\n private handleMessage(raw: string): void {\n const message = parseInbound(raw);\n if (!message) return;\n\n // Command response (has an id matching a pending request).\n if (typeof message.id === 'number' && this.pending.has(message.id)) {\n const waiter = this.pending.get(message.id);\n this.pending.delete(message.id);\n if (waiter) {\n if (message.error) waiter.reject(new Error(message.error.message));\n else waiter.resolve(message.result);\n }\n return;\n }\n\n // Event (buffered for the Phase 1 stream tools).\n if (typeof message.method !== 'string') return;\n if (!this.buffers.has(message.method as CdpEventName)) return;\n const event = message.method as CdpEventName;\n const buffer = this.buffers.get(event);\n if (!buffer) return;\n buffer.push(message.params);\n if (buffer.length > this.bufferSize) buffer.shift();\n this.emitter.emit(event, message.params);\n }\n\n getBufferedEvents<E extends CdpEventName>(event: E): ReadonlyArray<CdpEventMap[E]> {\n const buffer = this.buffers.get(event);\n return (buffer ?? []) as ReadonlyArray<CdpEventMap[E]>;\n }\n\n on<E extends CdpEventName>(event: E, listener: (payload: CdpEventMap[E]) => void): () => void {\n this.emitter.on(event, listener as (payload: unknown) => void);\n return () => this.emitter.off(event, listener as (payload: unknown) => void);\n }\n\n /** Close the relay client websocket and reject any in-flight commands. */\n close(): void {\n this.ws?.close();\n this.ws = null;\n for (const waiter of this.pending.values()) {\n waiter.reject(new Error('Chii relay connection closed.'));\n }\n this.pending.clear();\n }\n}\n","/**\n * Boots the local Chii relay server.\n *\n * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome\n * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.\n * The relay accepts a `target` websocket from the phone's injected `target.js`\n * and `client` websockets from CDP frontends (our MCP connection).\n *\n * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app\n * entries.\n */\n\nimport { createServer, type Server } from 'node:http';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\n\n/** `chii/server` is CommonJS and shipped without TypeScript types. */\ninterface ChiiServerModule {\n start(options: {\n port?: number;\n host?: string;\n domain?: string;\n server?: Server;\n basePath?: string;\n }): Promise<void>;\n}\n\nfunction loadChiiServer(): ChiiServerModule {\n // `chii`'s package `main` is `./server/index.js`, exposing `{ start }`.\n const mod: unknown = require('chii');\n if (\n typeof mod === 'object' &&\n mod !== null &&\n 'start' in mod &&\n typeof (mod as { start: unknown }).start === 'function'\n ) {\n return mod as ChiiServerModule;\n }\n throw new Error('chii server module did not expose start()');\n}\n\nexport interface ChiiRelay {\n port: number;\n /** Base URL for the relay HTTP/WS server, e.g. `http://127.0.0.1:9100`. */\n baseUrl: string;\n close(): Promise<void>;\n}\n\nexport interface StartChiiRelayOptions {\n /** Local port for the relay. Default 9100. */\n port?: number;\n /** Bind host. Default 127.0.0.1 (tunnel reaches it locally). */\n host?: string;\n}\n\n/** Starts the Chii relay on the given port and resolves once listening. */\nexport async function startChiiRelay(options: StartChiiRelayOptions = {}): Promise<ChiiRelay> {\n const port = options.port ?? 9100;\n const host = options.host ?? '127.0.0.1';\n\n const httpServer = createServer();\n const chii = loadChiiServer();\n // Passing an existing `server` makes chii attach its Koa handler + WS upgrade\n // to our HTTP server rather than creating its own listener.\n await chii.start({ server: httpServer, domain: `${host}:${port}`, port });\n\n await new Promise<void>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(port, host, () => {\n httpServer.off('error', reject);\n resolve();\n });\n });\n\n return {\n port,\n baseUrl: `http://${host}:${port}`,\n close: () =>\n new Promise<void>((resolve) => {\n httpServer.close(() => resolve());\n }),\n };\n}\n","/**\n * Build a self-attaching dogfood deep link.\n *\n * `ait deploy --scheme-only` prints an `intoss-private://…?_deploymentId=<uuid>`\n * URL that opens a dogfood bundle on a phone. The in-app debug gate\n * (`src/in-app/gate.ts`) already auto-attaches when the entry URL also carries\n * `debug=1` and `relay=<wss-url>` — no QR scan or paste needed. This helper\n * splices those two params into the scheme URL so opening the result (e.g. via\n * `adb shell am start -d \"<url>\"`) attaches the running mini-app to the live\n * Chii relay with zero human input.\n *\n * The Toss app propagates extra query params from the entry deep link into the\n * mini-app WebView's `location.search` (confirmed behavior), so the gate reads\n * them at attach time.\n *\n * Why not `URL`/`URLSearchParams`: `intoss-private:` is a non-special scheme.\n * The WHATWG `URL` parser treats such schemes opaquely (no host/path/query\n * decomposition you can rely on across runtimes), so query manipulation via\n * `url.searchParams` is not portable here. We splice the query string directly\n * on the raw string instead, which keeps the scheme, authority, path, and any\n * pre-existing params (notably `_deploymentId`) byte-for-byte intact.\n */\n\n/** A param the helper appends. Existing occurrences are replaced, not duplicated. */\ntype AppendParam = readonly [key: string, value: string];\n\nfunction stripExisting(query: string, key: string): string {\n if (query === '') return '';\n return query\n .split('&')\n .filter((pair) => pair !== '' && pair.split('=')[0] !== key)\n .join('&');\n}\n\n/**\n * Splices `debug=1` and `relay=<wssUrl>` into a scheme URL's query string,\n * preserving everything else (scheme, authority, path, hash, and the existing\n * `_deploymentId` param). If `debug` or `relay` is already present it is\n * replaced so the helper is idempotent.\n *\n * @param schemeUrl - The `intoss-private://…?_deploymentId=<uuid>` URL printed\n * by `ait deploy --scheme-only`. Must already carry `_deploymentId` (Layer B\n * of the gate); this helper does not invent one.\n * @param wssUrl - The live relay URL (`wss://…trycloudflare.com`) from the\n * running debug MCP server's quick tunnel.\n * @returns The same URL with `debug=1&relay=<encoded wssUrl>` appended.\n * @throws If `wssUrl` is not a `wss:` URL (the gate rejects anything else, so\n * producing such a link would be a silent dead end).\n */\nexport function buildDeepLinkAttachUrl(schemeUrl: string, wssUrl: string): string {\n let relay: URL;\n try {\n relay = new URL(wssUrl);\n } catch {\n throw new Error(`relay URL is not a valid URL: ${wssUrl}`);\n }\n if (relay.protocol !== 'wss:') {\n throw new Error(`relay URL must use the wss: scheme, got ${relay.protocol} (${wssUrl})`);\n }\n\n const hashIndex = schemeUrl.indexOf('#');\n const hash = hashIndex === -1 ? '' : schemeUrl.slice(hashIndex);\n const beforeHash = hashIndex === -1 ? schemeUrl : schemeUrl.slice(0, hashIndex);\n\n const queryIndex = beforeHash.indexOf('?');\n const base = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);\n let query = queryIndex === -1 ? '' : beforeHash.slice(queryIndex + 1);\n\n const appended: AppendParam[] = [\n ['debug', '1'],\n ['relay', wssUrl],\n ];\n for (const [key] of appended) {\n query = stripExisting(query, key);\n }\n for (const [key, value] of appended) {\n const pair = `${key}=${encodeURIComponent(value)}`;\n query = query === '' ? pair : `${query}&${pair}`;\n }\n\n return `${base}?${query}${hash}`;\n}\n","/**\n * Debug-mode MCP tools (Phase 1–3).\n *\n * Read-only tools that normalize CDP / AIT data into `chrome-devtools-mcp`-\n * compatible shapes. The tools never touch a websocket or HTTP endpoint\n * directly — they read from an injected `CdpConnection` (CDP events/commands)\n * or `AitSource` (AIT.* domain), which is what makes them unit-testable with a\n * fake. No phone and no running dev server are needed in tests.\n *\n * Phase 1 (CDP events):\n * - `list_console_messages` ← Runtime.consoleAPICalled\n * - `list_network_requests` ← Network.requestWillBeSent + responseReceived\n * - `list_pages` ← Chii relay target list + tunnel status\n * Phase 2 (CDP commands):\n * - `get_dom_document` ← DOM.getDocument\n * - `take_snapshot` ← DOMSnapshot.captureSnapshot\n * - `take_screenshot` ← Page.captureScreenshot\n * Phase 3 (AIT.* domain — CDP can't cover these):\n * - `AIT.getSdkCallHistory`\n * - `AIT.getMockState`\n * - `AIT.getOperationalEnvironment`\n */\n\nimport type {\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\nimport type {\n CdpConnection,\n CdpRemoteObject,\n ConsoleApiCalledEvent,\n DomGetDocumentResult,\n DomSnapshotResult,\n NetworkRequestWillBeSentEvent,\n NetworkResponseReceivedEvent,\n} from './cdp-connection.js';\nimport { buildDeepLinkAttachUrl } from './deeplink.js';\n\n/** Tunnel state surfaced by `list_pages`. */\nexport interface TunnelStatus {\n /** Whether the cloudflared quick tunnel is up. */\n up: boolean;\n /** Public `wss://*.trycloudflare.com` relay URL the phone attaches to. */\n wssUrl: string | null;\n}\n\n/** Static MCP tool descriptors (name + JSONSchema) for the full debug tool surface. */\nexport const DEBUG_TOOL_DEFINITIONS = [\n {\n name: 'list_console_messages',\n description:\n 'Lists recent console messages (console.log/warn/error/info) captured from the attached ' +\n 'mini-app page over CDP (Runtime.consoleAPICalled). Read-only. Returns level, text, ' +\n 'timestamp, and stringified args, oldest-first.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'list_network_requests',\n description:\n 'Lists recent network requests (XHR/fetch) captured from the attached mini-app page over ' +\n 'CDP (Network.requestWillBeSent + Network.responseReceived). Read-only. Returns url, ' +\n 'method, status, and timing, oldest-first.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'list_pages',\n description:\n 'Lists the mini-app page(s) the Chii relay currently sees attached, plus whether the ' +\n 'cloudflared tunnel is up and the public wss relay URL the phone uses to attach. ' +\n 'Call this first to confirm a page is attached before reading console/network.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'build_attach_url',\n description:\n 'Turns an `ait deploy --scheme-only` URL (intoss-private://…?_deploymentId=<uuid>) into a ' +\n 'self-attaching deep link by splicing in debug=1 and the live relay URL for this session. ' +\n 'Opening the result on the phone (e.g. `adb shell am start -d \"<url>\"`) attaches the mini-app ' +\n 'to this debug session with no QR scan. Requires the tunnel to be up — call list_pages first.',\n inputSchema: {\n type: 'object',\n properties: {\n scheme_url: {\n type: 'string',\n description:\n 'The intoss-private:// scheme URL from `ait deploy --scheme-only` (must carry _deploymentId).',\n },\n },\n required: ['scheme_url'],\n },\n },\n {\n name: 'get_dom_document',\n description:\n 'Returns the DOM tree of the attached mini-app page over CDP (DOM.getDocument). Read-only. ' +\n 'Use for structural/layout regression diagnosis (e.g. confirming an element exists, ' +\n 'inspecting attributes). Returns the document root node with children.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'take_snapshot',\n description:\n 'Captures a serialized snapshot of the attached page over CDP (DOMSnapshot.captureSnapshot). ' +\n 'Read-only. Returns the documents + interned strings table for visual-regression diagnosis ' +\n '(e.g. checking computed CSS custom properties like --sat against the live layout).',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'take_screenshot',\n description:\n 'Captures a PNG screenshot of the attached mini-app page over CDP (Page.captureScreenshot) ' +\n 'so the agent can see the phone screen directly. Read-only. Returns an image content block.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getSdkCallHistory',\n description:\n 'Returns the recent Apps In Toss SDK call trace (method, args, result/error, timestamp) that ' +\n 'raw CDP cannot observe. Read-only. Use to confirm an SDK call fired and how it resolved ' +\n '(e.g. a saveBase64Data permission regression).',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getMockState',\n description:\n 'Returns the devtools mock state snapshot (window.__ait) — environment, permissions, location, ' +\n 'auth, network, IAP, and more. Read-only. In dev mode this is the live browser mock state; in ' +\n 'debug mode the in-app side reports it over the AIT domain.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getOperationalEnvironment',\n description:\n 'Returns getOperationalEnvironment() plus the resolved SDK version — metadata raw CDP cannot ' +\n 'observe. Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n] as const;\n\nexport type DebugToolName = (typeof DEBUG_TOOL_DEFINITIONS)[number]['name'];\n\nconst DEBUG_TOOL_NAMES = new Set<string>(DEBUG_TOOL_DEFINITIONS.map((t) => t.name));\n\nexport function isDebugToolName(name: string): name is DebugToolName {\n return DEBUG_TOOL_NAMES.has(name);\n}\n\n/** Normalized console message returned by `list_console_messages`. */\nexport interface ConsoleMessage {\n level: string;\n text: string;\n timestamp: number;\n args: string[];\n}\n\n/** Normalized network request returned by `list_network_requests`. */\nexport interface NetworkRequest {\n requestId: string;\n url: string;\n method: string;\n /** HTTP status once a response was seen, else null (still in-flight). */\n status: number | null;\n statusText: string | null;\n /** Request start (CDP timestamp). */\n startTime: number;\n /** Response received (CDP timestamp), else null. */\n endTime: number | null;\n}\n\n/** Renders a CDP `RemoteObject` console arg to a stable display string. */\nfunction renderRemoteObject(arg: CdpRemoteObject): string {\n if (arg.value !== undefined) {\n if (typeof arg.value === 'string') return arg.value;\n try {\n return JSON.stringify(arg.value);\n } catch {\n return String(arg.value);\n }\n }\n if (arg.description !== undefined) return arg.description;\n if (arg.className !== undefined) return arg.className;\n return arg.subtype ?? arg.type;\n}\n\nexport function normalizeConsoleMessage(event: ConsoleApiCalledEvent): ConsoleMessage {\n const args = event.args.map(renderRemoteObject);\n return {\n level: event.type,\n text: args.join(' '),\n timestamp: event.timestamp,\n args,\n };\n}\n\nexport function listConsoleMessages(connection: CdpConnection): ConsoleMessage[] {\n return connection\n .getBufferedEvents('Runtime.consoleAPICalled')\n .map((event) => normalizeConsoleMessage(event));\n}\n\nexport function listNetworkRequests(connection: CdpConnection): NetworkRequest[] {\n const requests = connection.getBufferedEvents('Network.requestWillBeSent');\n const responses = connection.getBufferedEvents('Network.responseReceived');\n\n const responseByRequestId = new Map<string, NetworkResponseReceivedEvent>();\n for (const response of responses) {\n responseByRequestId.set(response.requestId, response);\n }\n\n return requests.map((request: NetworkRequestWillBeSentEvent) => {\n const response = responseByRequestId.get(request.requestId);\n return {\n requestId: request.requestId,\n url: request.request.url,\n method: request.request.method,\n status: response ? response.response.status : null,\n statusText: response ? response.response.statusText : null,\n startTime: request.timestamp,\n endTime: response ? response.timestamp : null,\n };\n });\n}\n\n/** Result of `list_pages`: attach status + tunnel state. */\nexport interface ListPagesResult {\n pages: ReturnType<CdpConnection['listTargets']>;\n tunnel: TunnelStatus;\n}\n\nexport function listPages(connection: CdpConnection, tunnel: TunnelStatus): ListPagesResult {\n return { pages: connection.listTargets(), tunnel };\n}\n\n/** A `build_attach_url` result: the spliced deep link the phone should open. */\nexport interface BuildAttachUrlResult {\n /** The scheme URL with `debug=1&relay=<wss>` spliced in. */\n attachUrl: string;\n /** The relay URL that was spliced in (this session's quick tunnel). */\n relayUrl: string;\n}\n\n/**\n * Builds a self-attaching dogfood deep link from an `ait deploy --scheme-only`\n * URL plus this session's live relay. Throws if the tunnel is not up yet (no\n * relay URL to splice in) — the caller surfaces that as a tool error.\n */\nexport function buildAttachUrl(schemeUrl: string, tunnel: TunnelStatus): BuildAttachUrlResult {\n if (!tunnel.up || tunnel.wssUrl === null) {\n throw new Error(\n 'No relay URL yet — the cloudflared quick tunnel is not up. ' +\n 'Call list_pages to check tunnel status.',\n );\n }\n return {\n attachUrl: buildDeepLinkAttachUrl(schemeUrl, tunnel.wssUrl),\n relayUrl: tunnel.wssUrl,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 2 — DOM / snapshot / screenshot (CDP commands) */\n/* -------------------------------------------------------------------------- */\n\n/** Returns the DOM tree of the attached page (`DOM.getDocument`). */\nexport function getDomDocument(connection: CdpConnection): Promise<DomGetDocumentResult> {\n // `pierce: true` flattens shadow roots; depth -1 returns the whole subtree so\n // a single call yields the full tree for structural diagnosis.\n return connection.send('DOM.getDocument', { depth: -1, pierce: true });\n}\n\n/** Returns a serialized page snapshot (`DOMSnapshot.captureSnapshot`). */\nexport function takeSnapshot(connection: CdpConnection): Promise<DomSnapshotResult> {\n return connection.send('DOMSnapshot.captureSnapshot', {});\n}\n\n/** A `take_screenshot` result: the raw base64 PNG plus a ready-to-use data URI. */\nexport interface ScreenshotResult {\n /** Base64-encoded PNG bytes (no data-URI prefix). */\n data: string;\n /** `data:image/png;base64,…` form for clients that render a URI. */\n dataUri: string;\n mimeType: 'image/png';\n}\n\n/** Captures a PNG screenshot of the attached page (`Page.captureScreenshot`). */\nexport async function takeScreenshot(connection: CdpConnection): Promise<ScreenshotResult> {\n const { data } = await connection.send('Page.captureScreenshot', { format: 'png' });\n return { data, dataUri: `data:image/png;base64,${data}`, mimeType: 'image/png' };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 3 — AIT.* domain (CDP can't cover these) */\n/* -------------------------------------------------------------------------- */\n\n/** Set of tool names served by the AIT source rather than the CDP connection. */\nconst AIT_TOOL_NAMES = new Set<string>([\n 'AIT.getSdkCallHistory',\n 'AIT.getMockState',\n 'AIT.getOperationalEnvironment',\n]);\n\n/** True for the Phase 3 AIT.* tools (served by an `AitSource`, not CDP). */\nexport function isAitToolName(name: string): boolean {\n return AIT_TOOL_NAMES.has(name);\n}\n\n/** Returns the recent SDK call trace (`AIT.getSdkCallHistory`). */\nexport function getSdkCallHistory(source: AitSource): Promise<AitSdkCallHistory> {\n return source.get('AIT.getSdkCallHistory');\n}\n\n/** Returns the devtools mock-state snapshot (`AIT.getMockState`). */\nexport function getMockState(source: AitSource): Promise<AitMockState> {\n return source.get('AIT.getMockState');\n}\n\n/** Returns the operational environment + SDK version (`AIT.getOperationalEnvironment`). */\nexport function getOperationalEnvironment(source: AitSource): Promise<AitOperationalEnvironment> {\n return source.get('AIT.getOperationalEnvironment');\n}\n","/**\n * cloudflared quick tunnel + attach banner for the debug-mode MCP server.\n *\n * On spawn, the debug server opens an accountless `*.trycloudflare.com` quick\n * tunnel to the local Chii relay so the phone can attach over a public wss URL,\n * then prints that URL + an attach token + an ASCII QR to the terminal. The\n * phone scans the QR (or pastes the URL) to attach; the in-app side passes the\n * token back. The token is generated + displayed as a pairing hint; relay-side\n * validation (ACL enforcement) is a later phase.\n *\n * Node-only: spawns the cloudflared binary and writes to stdout/stderr.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { bin, install, Tunnel } from 'cloudflared';\nimport qrcode from 'qrcode-terminal';\n\n/** Generates a 32-byte hex attach token shown as a pairing hint (relay-side validation is a later phase). */\nexport function generateAttachToken(): string {\n return randomBytes(32).toString('hex');\n}\n\nexport interface QuickTunnel {\n /** Public `https://*.trycloudflare.com` URL the tunnel exposes. */\n url: string;\n /** Same host as `wss://` — the relay endpoint the phone attaches to. */\n wssUrl: string;\n stop(): void;\n}\n\n/** Ensures the cloudflared binary is installed (downloads + caches on first run). */\nasync function ensureCloudflaredBin(): Promise<void> {\n const { existsSync } = await import('node:fs');\n if (!existsSync(bin)) {\n await install(bin);\n }\n}\n\n/**\n * Opens a cloudflared quick tunnel to the local relay port and resolves once\n * the public URL is assigned.\n */\nexport async function startQuickTunnel(localPort: number): Promise<QuickTunnel> {\n await ensureCloudflaredBin();\n\n const tunnel = Tunnel.quick(`http://127.0.0.1:${localPort}`);\n\n const url = await new Promise<string>((resolve, reject) => {\n const onUrl = (assigned: string) => {\n cleanup();\n resolve(assigned);\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onExit = (code: number | null) => {\n cleanup();\n reject(new Error(`cloudflared exited before assigning a URL (code ${code})`));\n };\n const cleanup = () => {\n tunnel.off('url', onUrl);\n tunnel.off('error', onError);\n tunnel.off('exit', onExit);\n };\n tunnel.once('url', onUrl);\n tunnel.once('error', onError);\n tunnel.once('exit', onExit);\n });\n\n return {\n url,\n wssUrl: url.replace(/^https/, 'wss'),\n stop: () => {\n tunnel.stop();\n },\n };\n}\n\nexport interface AttachBannerInput {\n wssUrl: string;\n token: string;\n}\n\n/** Renders the attach banner (URL + token + ASCII QR) as a string. */\nexport async function renderAttachBanner(input: AttachBannerInput): Promise<string> {\n // Encode the attach payload as a URL so a QR scan opens directly.\n const payload = `${input.wssUrl}?token=${input.token}`;\n const qr = await new Promise<string>((resolve) => {\n qrcode.generate(payload, { small: true }, (rendered) => resolve(rendered));\n });\n return [\n '',\n 'AIT debug — attach a mini-app to this session',\n '',\n ` relay (wss): ${input.wssUrl}`,\n ` attach token: ${input.token}`,\n ` (token is a pairing hint — relay-side validation lands in a later phase)`,\n '',\n ' Open the dogfood mini-app with ?debug=1, then scan the QR',\n ' (or paste the relay URL + token in the in-app attach form):',\n '',\n qr,\n ].join('\\n');\n}\n\n/** Prints the attach banner to stderr (stdout is the MCP stdio channel). */\nexport async function printAttachBanner(input: AttachBannerInput): Promise<void> {\n const banner = await renderAttachBanner(input);\n process.stderr.write(`${banner}\\n`);\n}\n","/**\n * @ait-co/devtools debug-mode MCP server (stdio).\n *\n * Lets an AI coding agent attach to a running mini-app (real Toss WebView, or a\n * browser in dev mode) and read its console/network/DOM/screenshot over CDP plus\n * the AIT.* domain, without a human watching a phone. Transport is CDP-via-Chii:\n * a local Chii relay :9100 exposed through a cloudflared quick tunnel; the phone\n * attaches over the public wss URL.\n *\n * AI host --stdio--> this server --CDP client WS--> Chii relay :9100\n * ^-- target WS -- phone\n *\n * The tool layer reads from an injectable `CdpConnection` (CDP) and `AitSource`\n * (AIT.*), so every tool is unit-testable with a fake (no phone). This module\n * wires the live pieces (relay + tunnel + production connection); the phone\n * roundtrip is fully wired and pending only on-device acceptance.\n *\n * Node-only.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { ChiiAitSource } from './ait-chii-source.js';\nimport type { AitSource } from './ait-source.js';\nimport type { CdpConnection } from './cdp-connection.js';\nimport { ChiiCdpConnection } from './chii-connection.js';\nimport { startChiiRelay } from './chii-relay.js';\nimport {\n buildAttachUrl,\n DEBUG_TOOL_DEFINITIONS,\n getDomDocument,\n getMockState,\n getOperationalEnvironment,\n getSdkCallHistory,\n isAitToolName,\n isDebugToolName,\n listConsoleMessages,\n listNetworkRequests,\n listPages,\n type TunnelStatus,\n takeScreenshot,\n takeSnapshot,\n} from './tools.js';\nimport {\n generateAttachToken,\n printAttachBanner,\n type QuickTunnel,\n startQuickTunnel,\n} from './tunnel.js';\n\n/** Live infra the connection reads tunnel status from. */\nexport interface DebugServerDeps {\n connection: CdpConnection;\n /** AIT.* domain source — forwarded over the same Chii channel in production. */\n aitSource: AitSource;\n /** Returns current tunnel status (URL changes per spawn). */\n getTunnelStatus(): TunnelStatus;\n}\n\n/**\n * Builds the debug-mode MCP server around an injected CDP connection + AIT\n * source + tunnel status getter. Pure wiring — does not start a relay or\n * tunnel, which is what makes the tool surface unit-testable.\n */\nexport function createDebugServer(deps: DebugServerDeps): Server {\n const { connection, aitSource, getTunnelStatus } = deps;\n\n const server = new Server(\n { name: 'ait-debug', version: __VERSION__ },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: DEBUG_TOOL_DEFINITIONS.map((tool) => ({ ...tool })),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n if (!isDebugToolName(name)) {\n return {\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n\n // AIT.* tools are served by the AIT source. In production it rides the same\n // Chii websocket as CDP, so the connection must be attached first; the AIT\n // source's sendCommand rejects with a clear message if no page is attached.\n if (isAitToolName(name)) {\n try {\n await connection.enableDomains();\n switch (name) {\n case 'AIT.getSdkCallHistory':\n return jsonResult(await getSdkCallHistory(aitSource));\n case 'AIT.getMockState':\n return jsonResult(await getMockState(aitSource));\n case 'AIT.getOperationalEnvironment':\n return jsonResult(await getOperationalEnvironment(aitSource));\n default:\n return unknownTool(name);\n }\n } catch (err) {\n return errorResult(err, name);\n }\n }\n\n // build_attach_url is pure synthesis (scheme URL + relay URL → deep link).\n // It works before any page attaches, so it must not require enableDomains.\n if (name === 'build_attach_url') {\n const schemeUrl = request.params.arguments?.scheme_url;\n if (typeof schemeUrl !== 'string' || schemeUrl === '') {\n return {\n content: [{ type: 'text', text: 'build_attach_url requires a non-empty scheme_url.' }],\n isError: true,\n };\n }\n try {\n return jsonResult(buildAttachUrl(schemeUrl, getTunnelStatus()));\n } catch (err) {\n return errorResult(err, name);\n }\n }\n\n try {\n // Ensure CDP domains are enabled before reading. No-op once attached;\n // throws a clear message while no page is attached yet.\n await connection.enableDomains();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (name === 'list_pages') {\n // list_pages is still useful pre-attach: report tunnel + empty pages.\n return jsonResult(listPages(connection, getTunnelStatus()));\n }\n return {\n content: [\n {\n type: 'text',\n text: `${message}\\nCall list_pages to confirm a mini-app has attached over the relay.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n switch (name) {\n case 'list_console_messages':\n return jsonResult(listConsoleMessages(connection));\n case 'list_network_requests':\n return jsonResult(listNetworkRequests(connection));\n case 'list_pages':\n return jsonResult(listPages(connection, getTunnelStatus()));\n case 'get_dom_document':\n return jsonResult(await getDomDocument(connection));\n case 'take_snapshot':\n return jsonResult(await takeSnapshot(connection));\n case 'take_screenshot': {\n const shot = await takeScreenshot(connection);\n return {\n content: [{ type: 'image' as const, data: shot.data, mimeType: shot.mimeType }],\n };\n }\n default:\n return unknownTool(name);\n }\n } catch (err) {\n return errorResult(err, name);\n }\n });\n\n return server;\n}\n\nfunction jsonResult(value: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }] };\n}\n\nfunction unknownTool(name: string) {\n return { content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }], isError: true };\n}\n\nfunction errorResult(err: unknown, name: string) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [\n {\n type: 'text' as const,\n text: `${name} failed: ${message}\\nCall list_pages to confirm a mini-app has attached over the relay.`,\n },\n ],\n isError: true,\n };\n}\n\nexport interface RunDebugServerOptions {\n /** Local Chii relay port. Default 9100. */\n relayPort?: number;\n}\n\n/**\n * Boots the live debug stack and serves it over stdio:\n * 1. start the Chii relay,\n * 2. open a cloudflared quick tunnel to it,\n * 3. print QR + secret token,\n * 4. expose the debug tools backed by a `ChiiCdpConnection` + `ChiiAitSource`.\n */\nexport async function runDebugServer(options: RunDebugServerOptions = {}): Promise<void> {\n const relayPort = options.relayPort ?? 9100;\n\n const relay = await startChiiRelay({ port: relayPort });\n\n let tunnel: QuickTunnel | null = null;\n let tunnelStatus: TunnelStatus = { up: false, wssUrl: null };\n const token = generateAttachToken();\n\n try {\n tunnel = await startQuickTunnel(relayPort);\n tunnelStatus = { up: true, wssUrl: tunnel.wssUrl };\n await printAttachBanner({ wssUrl: tunnel.wssUrl, token });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[ait-debug] Failed to open cloudflared quick tunnel: ${message}\\n` +\n '[ait-debug] The relay is up locally; attach over the public URL is unavailable until the tunnel starts.\\n',\n );\n }\n\n const connection = new ChiiCdpConnection({ relayBaseUrl: relay.baseUrl });\n // AIT.* methods ride the same Chii channel as CDP commands.\n const aitSource = new ChiiAitSource(connection);\n const server = createDebugServer({\n connection,\n aitSource,\n getTunnelStatus: () => tunnelStatus,\n });\n\n const transport = new StdioServerTransport();\n\n const shutdown = () => {\n connection.close();\n tunnel?.stop();\n void relay.close();\n void server.close();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n\n await server.connect(transport);\n}\n","/**\n * Dev-mode `AitSource` — backed by the Vite dev server's mock-state endpoint.\n *\n * The dev server already exposes the live browser mock state at\n * `GET /api/ait-devtools/state` (registered by the unplugin with `mcp: true`).\n * Phase 3 aligns dev mode and debug mode on the same `AIT.*` tool surface, so\n * dev mode serves those tools off this one HTTP source instead of a CDP channel:\n *\n * - `AIT.getMockState` → the full state snapshot (verbatim).\n * - `AIT.getOperationalEnvironment` → derived from the snapshot's\n * `environment` + `appVersion` fields.\n * - `AIT.getSdkCallHistory` → empty (the dev endpoint does not record\n * an SDK call trace — honest, not faked).\n *\n * An AI agent thus sees the same `AIT.getMockState` tool whether attached to a\n * phone (debug) or a dev browser (dev). Tests inject a fake `fetch`.\n */\n\nimport type {\n AitMethodMap,\n AitMethodName,\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\n\n/** Minimal `fetch` shape this source needs (injectable in tests). */\nexport type FetchLike = (url: string) => Promise<{\n ok: boolean;\n status: number;\n statusText: string;\n json(): Promise<unknown>;\n}>;\n\nexport interface HttpAitSourceOptions {\n /** Full URL of the mock-state endpoint, e.g. `http://localhost:5173/api/ait-devtools/state`. */\n stateEndpoint: string;\n /** Injected for tests; defaults to global `fetch`. */\n fetchImpl?: FetchLike;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport class HttpAitSource implements AitSource {\n private readonly stateEndpoint: string;\n private readonly fetchImpl: FetchLike;\n\n constructor(options: HttpAitSourceOptions) {\n this.stateEndpoint = options.stateEndpoint;\n this.fetchImpl = options.fetchImpl ?? ((url) => fetch(url));\n }\n\n private async fetchState(): Promise<AitMockState> {\n const res = await this.fetchImpl(this.stateEndpoint);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch mock state from ${this.stateEndpoint}: HTTP ${res.status} ${res.statusText}. ` +\n 'Ensure the Vite dev server is running with the @ait-co/devtools unplugin option `mcp: true`.',\n );\n }\n const body = await res.json();\n return isObject(body) ? body : {};\n }\n\n async get<M extends AitMethodName>(method: M): Promise<AitMethodMap[M]> {\n switch (method) {\n case 'AIT.getMockState': {\n const state = await this.fetchState();\n return state as AitMethodMap[M];\n }\n case 'AIT.getOperationalEnvironment': {\n const state = await this.fetchState();\n const environment = typeof state.environment === 'string' ? state.environment : 'unknown';\n const sdkVersion = typeof state.appVersion === 'string' ? state.appVersion : null;\n const result: AitOperationalEnvironment = { environment, sdkVersion };\n return result as AitMethodMap[M];\n }\n case 'AIT.getSdkCallHistory': {\n // Dev endpoint records no SDK call trace; return empty rather than fake.\n const result: AitSdkCallHistory = { calls: [] };\n return result as AitMethodMap[M];\n }\n default:\n throw new Error(`Unknown AIT method: ${String(method)}`);\n }\n }\n}\n","/**\n * @ait-co/devtools dev-mode MCP server (stdio).\n *\n * Exposes the live browser mock state from a running Vite dev server to AI\n * coding agents via the Model Context Protocol (MCP).\n *\n * Architecture:\n * Browser (aitState) → Vite dev server endpoint (/api/ait-devtools/state)\n * ← HTTP GET ← this stdio MCP server ← AI agent\n *\n * The Vite endpoint is registered by the unplugin when `mcp: true` is set in\n * the plugin options (see `src/unplugin/index.ts`).\n *\n * Phase 3 tool-surface alignment: dev mode and debug mode now expose the same\n * `AIT.*` tools (`AIT.getMockState`, `AIT.getOperationalEnvironment`,\n * `AIT.getSdkCallHistory`). In dev mode they are backed by the HTTP mock-state\n * endpoint (see `HttpAitSource`); in debug mode by the Chii channel. So an AI\n * sees a coherent tool whether attached to a phone (debug) or a dev browser\n * (dev). `devtools_get_mock_state` (the original devtools#130 name) is kept as a\n * backward-compatible alias of `AIT.getMockState`.\n *\n * This module is reached via the `devtools-mcp --mode=dev` CLI entry (see\n * `cli.ts`); the default (no flag) bin mode is the debug-mode CDP/Chii server.\n *\n * Usage (in your MCP client config, e.g. Claude Desktop):\n * {\n * \"mcpServers\": {\n * \"ait-devtools\": {\n * \"command\": \"pnpm\",\n * \"args\": [\"exec\", \"devtools-mcp\", \"--mode=dev\"],\n * \"env\": { \"AIT_DEVTOOLS_URL\": \"http://localhost:5173\" }\n * }\n * }\n * }\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { HttpAitSource } from './ait-http-source.js';\nimport type { AitSource } from './ait-source.js';\nimport {\n getMockState,\n getOperationalEnvironment,\n getSdkCallHistory,\n isAitToolName,\n} from './tools.js';\n\n/** Tool descriptors served by the dev-mode server. */\nconst DEV_TOOL_DEFINITIONS = [\n {\n name: 'AIT.getMockState',\n description:\n 'Returns the devtools mock state snapshot (window.__ait) from the running browser session — ' +\n 'environment, permissions, location, auth, network, IAP, and more. Read-only. ' +\n 'Requires the Vite dev server running with the @ait-co/devtools unplugin option `mcp: true`. ' +\n 'Same tool as in debug mode, where the in-app side reports it over the AIT domain.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getOperationalEnvironment',\n description:\n 'Returns the operational environment + SDK/app version derived from the dev mock state. ' +\n 'Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getSdkCallHistory',\n description:\n 'Returns the SDK call trace. In dev mode the HTTP mock-state endpoint records no trace, so ' +\n 'this returns an empty list; in debug mode it is populated over the AIT domain. Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'devtools_get_mock_state',\n description:\n 'Backward-compatible alias of AIT.getMockState (the original devtools#130 name). Returns the ' +\n 'current AIT DevTools mock state snapshot. Read-only. Prefer AIT.getMockState in new configs.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n] as const;\n\nconst DEV_TOOL_NAMES = new Set<string>(DEV_TOOL_DEFINITIONS.map((t) => t.name));\n\nexport interface CreateDevServerDeps {\n /** AIT source for the dev tools. Defaults to an HTTP source over the dev server. */\n aitSource?: AitSource;\n}\n\n/** Builds the dev-mode MCP server (does not connect a transport). */\nexport function createDevServer(deps: CreateDevServerDeps = {}): Server {\n const devtoolsUrl = process.env.AIT_DEVTOOLS_URL ?? 'http://localhost:5173';\n const stateEndpoint = `${devtoolsUrl}/api/ait-devtools/state`;\n const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });\n\n const server = new Server(\n { name: 'ait-devtools', version: __VERSION__ },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n if (!DEV_TOOL_NAMES.has(name)) {\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n\n try {\n // `devtools_get_mock_state` is an alias of `AIT.getMockState`.\n const effective = name === 'devtools_get_mock_state' ? 'AIT.getMockState' : name;\n if (!isAitToolName(effective)) {\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n switch (effective) {\n case 'AIT.getMockState':\n return jsonResult(await getMockState(aitSource));\n case 'AIT.getOperationalEnvironment':\n return jsonResult(await getOperationalEnvironment(aitSource));\n case 'AIT.getSdkCallHistory':\n return jsonResult(await getSdkCallHistory(aitSource));\n default:\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [\n {\n type: 'text',\n text:\n `${message}\\n` +\n 'Is the Vite dev server running with the @ait-co/devtools unplugin option `mcp: true`? ' +\n 'Is AIT_DEVTOOLS_URL set correctly?',\n },\n ],\n isError: true,\n };\n }\n });\n\n return server;\n}\n\nfunction jsonResult(value: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }] };\n}\n\n/** Builds the dev-mode server and connects it over stdio. */\nexport async function runDevServer(): Promise<void> {\n const server = createDevServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","/**\n * `devtools-mcp` bin entry.\n *\n * Single bin, two transports selected by `--mode`:\n * - (default, no flag) debug mode — CDP/Chii relay + cloudflared quick tunnel.\n * Attach a running mini-app (real Toss WebView or a browser) and read its\n * console + network over CDP without a human watching a phone.\n * - `--mode=dev` — dev mode — reads the live browser mock state from a running\n * Vite dev server (the devtools#130 `devtools_get_mock_state` surface).\n *\n * Node-only stdio process.\n */\n\nimport { argv } from 'node:process';\nimport { fileURLToPath } from 'node:url';\nimport { runDebugServer } from './debug-server.js';\nimport { runDevServer } from './server.js';\n\ntype Mode = 'debug' | 'dev';\n\n/** Parses `--mode=<value>` / `--mode <value>` from argv; default `debug`. */\nexport function parseMode(argv: readonly string[]): Mode {\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === undefined) continue;\n if (arg.startsWith('--mode=')) {\n return normalizeMode(arg.slice('--mode='.length));\n }\n if (arg === '--mode') {\n const next = argv[i + 1];\n if (next === undefined) {\n throw new Error(\"--mode requires a value: 'debug' (default) or 'dev'.\");\n }\n return normalizeMode(next);\n }\n }\n return 'debug';\n}\n\nfunction normalizeMode(value: string): Mode {\n if (value === 'dev') return 'dev';\n if (value === 'debug') return 'debug';\n throw new Error(`Unknown --mode '${value}'. Expected 'debug' (default) or 'dev'.`);\n}\n\nasync function main(): Promise<void> {\n const mode = parseMode(process.argv.slice(2));\n if (mode === 'dev') {\n await runDevServer();\n } else {\n await runDebugServer();\n }\n}\n\n/** True when this file is the process entry (the bin), not an import. */\nfunction isEntrypoint(): boolean {\n const entry = argv[1];\n if (entry === undefined) return false;\n try {\n return fileURLToPath(import.meta.url) === entry;\n } catch {\n return false;\n }\n}\n\nif (isEntrypoint()) {\n main().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[devtools-mcp] fatal: ${message}\\n`);\n process.exitCode = 1;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,SAASA,WAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;;AAIhD,SAAS,iBAAiB,KAAiC;AACzD,KAAIA,WAAS,IAAI,IAAI,MAAM,QAAQ,IAAI,MAAM,CAC3C,QAAO,EAAE,OAAO,IAAI,OAAqC;AAE3D,QAAO,EAAE,OAAO,EAAE,EAAE;;;AAItB,SAAS,YAAY,KAA4B;AAC/C,QAAOA,WAAS,IAAI,GAAG,MAAM,EAAE;;;AAIjC,SAAS,yBAAyB,KAAyC;AAIzE,QAAO;EAAE,aAFPA,WAAS,IAAI,IAAI,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;EAErD,YADHA,WAAS,IAAI,IAAI,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;EACxD;;AAGpC,IAAa,gBAAb,MAAgD;CAC9C,YAAY,QAA2C;AAA1B,OAAA,SAAA;;CAE7B,MAAM,IAA6B,QAAqC;EACtE,MAAM,MAAM,MAAM,KAAK,OAAO,YAAY,OAAO;AAGjD,UAAQ,QAAR;GACE,KAAK,wBACH,QAAO,iBAAiB,IAAI;GAC9B,KAAK,mBACH,QAAO,YAAY,IAAI;GACzB,KAAK,gCACH,QAAO,yBAAyB,IAAI;GACtC,QACE,OAAM,IAAI,MAAM,uBAAuB,OAAO,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;AC9ChE,MAAM,sBAAsB;AAW5B,SAASC,WAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,SAAS,aAAa,KAAuC;CAC3D,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,SAAO;;AAET,KAAI,CAACA,WAAS,OAAO,CAAE,QAAO;CAC9B,MAAM,UAA6B,EAAE;AACrC,KAAI,OAAO,OAAO,OAAO,SAAU,SAAQ,KAAK,OAAO;AACvD,KAAI,OAAO,OAAO,WAAW,SAAU,SAAQ,SAAS,OAAO;AAC/D,KAAI,YAAY,OAAQ,SAAQ,SAAS,OAAO;AAChD,KAAI,YAAY,OAAQ,SAAQ,SAAS,OAAO;AAChD,KAAIA,WAAS,OAAO,MAAM,IAAI,OAAO,OAAO,MAAM,YAAY,SAC5D,SAAQ,QAAQ,EAAE,SAAS,OAAO,MAAM,SAAS;AAEnD,QAAO;;AAGT,MAAM,iBAA0C;CAC9C;CACA;CACA;CACD;;;;;AAaD,IAAa,oBAAb,MAAwD;CACtD;CACA;CACA,UAA2B,IAAI,cAAc;CAC7C,0BAA2B,IAAI,KAA8B;CAC7D,0BAA2B,IAAI,KAAwB;CAEvD,KAA+B;CAC/B,gBAAwB;;CAExB,kBAAgD;;CAEhD,0BAA2B,IAAI,KAG5B;CAEH,YAAY,SAAmC;AAC7C,OAAK,eAAe,QAAQ,aAAa,QAAQ,OAAO,GAAG;AAC3D,OAAK,aAAa,QAAQ,cAAc;AACxC,OAAK,MAAM,SAAS,eAAgB,MAAK,QAAQ,IAAI,OAAO,EAAE,CAAC;AAG/D,OAAK,QAAQ,gBAAgB,EAAE;;;CAIjC,MAAM,iBAAuC;EAC3C,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,aAAa,UAAU;AACvD,MAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,qCAAqC,IAAI,OAAO,GAAG,IAAI,aAAa;EAEtF,MAAM,OAAgB,MAAM,IAAI,MAAM;EACtC,MAAM,OAAOA,WAAS,KAAK,IAAI,MAAM,QAAQ,KAAK,QAAQ,GAAG,KAAK,UAAU,EAAE;AAC9E,OAAK,QAAQ,OAAO;AACpB,OAAK,MAAM,QAAQ,MAAM;AACvB,OAAI,CAACA,WAAS,KAAK,IAAI,OAAO,KAAK,OAAO,SAAU;AACpD,QAAK,QAAQ,IAAI,KAAK,IAAI;IACxB,IAAI,KAAK;IACT,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;IACrD,KAAK,OAAO,KAAK,QAAQ,WAAW,KAAK,MAAM;IAChD,CAAC;;AAEJ,SAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC;;CAGnC,cAA2B;AACzB,SAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC;;;;;;CAOnC,MAAM,gBAA+B;AACnC,MAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAAM;AAGtD,MAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,OAAK,kBAAkB,KAAK,kBAAkB,CAAC,cAAc;AAC3D,QAAK,kBAAkB;IACvB;AACF,SAAO,KAAK;;CAGd,MAAc,mBAAkC;EAE9C,MAAM,UADU,MAAM,KAAK,gBAAgB,EACpB;AACvB,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,mDAAmD;EAKrE,MAAM,KAAK,IAAI,UACb,GAHa,KAAK,aAAa,QAAQ,SAAS,KAAK,CAG3C,UAFK,gBAAgB,KAAK,KAAK,GAEZ,UAAU,mBAAmB,OAAO,GAAG,GACrE;AACD,OAAK,KAAK;AAEV,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,MAAG,KAAK,cAAc,SAAS,CAAC;AAChC,MAAG,KAAK,UAAU,QAAe,OAAO,IAAI,CAAC;IAC7C;AAEF,KAAG,GAAG,YAAY,SAA4B,KAAK,cAAc,KAAK,UAAU,CAAC,CAAC;AAElF,OAAK,kBAAkB,iBAAiB;AACxC,OAAK,kBAAkB,iBAAiB;AAGxC,OAAK,kBAAkB,aAAa;AACpC,OAAK,kBAAkB,cAAc;;;CAIvC,kBAA0B,QAAgB,SAAkC,EAAE,EAAQ;AACpF,MAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAAM;EACvD,MAAM,KAAK,KAAK;AAChB,OAAK,GAAG,KAAK,KAAK,UAAU;GAAE;GAAI;GAAQ;GAAQ,CAAC,CAAC;;;;;;CAOtD,KACE,QACA,QACqC;AACrC,SAAO,KAAK,YAAY,QAAQ,UAAU,EAAE,CAAC;;;;;;;CAQ/C,YAAY,QAAgB,SAAkC,EAAE,EAAoB;AAClF,MAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,KAC/C,QAAO,QAAQ,uBACb,IAAI,MAAM,+EAA+E,CAC1F;EAEH,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,KAAK;AAChB,SAAO,IAAI,SAAkB,SAAS,WAAW;AAC/C,QAAK,QAAQ,IAAI,IAAI;IAAE;IAAS;IAAQ,CAAC;AACzC,MAAG,KAAK,KAAK,UAAU;IAAE;IAAI;IAAQ;IAAQ,CAAC,CAAC;IAC/C;;CAGJ,cAAsB,KAAmB;EACvC,MAAM,UAAU,aAAa,IAAI;AACjC,MAAI,CAAC,QAAS;AAGd,MAAI,OAAO,QAAQ,OAAO,YAAY,KAAK,QAAQ,IAAI,QAAQ,GAAG,EAAE;GAClE,MAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ,GAAG;AAC3C,QAAK,QAAQ,OAAO,QAAQ,GAAG;AAC/B,OAAI,OACF,KAAI,QAAQ,MAAO,QAAO,OAAO,IAAI,MAAM,QAAQ,MAAM,QAAQ,CAAC;OAC7D,QAAO,QAAQ,QAAQ,OAAO;AAErC;;AAIF,MAAI,OAAO,QAAQ,WAAW,SAAU;AACxC,MAAI,CAAC,KAAK,QAAQ,IAAI,QAAQ,OAAuB,CAAE;EACvD,MAAM,QAAQ,QAAQ;EACtB,MAAM,SAAS,KAAK,QAAQ,IAAI,MAAM;AACtC,MAAI,CAAC,OAAQ;AACb,SAAO,KAAK,QAAQ,OAAO;AAC3B,MAAI,OAAO,SAAS,KAAK,WAAY,QAAO,OAAO;AACnD,OAAK,QAAQ,KAAK,OAAO,QAAQ,OAAO;;CAG1C,kBAA0C,OAAyC;AAEjF,SADe,KAAK,QAAQ,IAAI,MAAM,IACpB,EAAE;;CAGtB,GAA2B,OAAU,UAAyD;AAC5F,OAAK,QAAQ,GAAG,OAAO,SAAuC;AAC9D,eAAa,KAAK,QAAQ,IAAI,OAAO,SAAuC;;;CAI9E,QAAc;AACZ,OAAK,IAAI,OAAO;AAChB,OAAK,KAAK;AACV,OAAK,MAAM,UAAU,KAAK,QAAQ,QAAQ,CACxC,QAAO,uBAAO,IAAI,MAAM,gCAAgC,CAAC;AAE3D,OAAK,QAAQ,OAAO;;;;;;;;;;;;;;;;AC5OxB,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAa9C,SAAS,iBAAmC;CAE1C,MAAM,MAAe,QAAQ,OAAO;AACpC,KACE,OAAO,QAAQ,YACf,QAAQ,QACR,WAAW,OACX,OAAQ,IAA2B,UAAU,WAE7C,QAAO;AAET,OAAM,IAAI,MAAM,4CAA4C;;;AAkB9D,eAAsB,eAAe,UAAiC,EAAE,EAAsB;CAC5F,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,OAAO,QAAQ,QAAQ;CAE7B,MAAM,aAAa,cAAc;AAIjC,OAHa,gBAAgB,CAGlB,MAAM;EAAE,QAAQ;EAAY,QAAQ,GAAG,KAAK,GAAG;EAAQ;EAAM,CAAC;AAEzE,OAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,aAAW,KAAK,SAAS,OAAO;AAChC,aAAW,OAAO,MAAM,YAAY;AAClC,cAAW,IAAI,SAAS,OAAO;AAC/B,YAAS;IACT;GACF;AAEF,QAAO;EACL;EACA,SAAS,UAAU,KAAK,GAAG;EAC3B,aACE,IAAI,SAAe,YAAY;AAC7B,cAAW,YAAY,SAAS,CAAC;IACjC;EACL;;;;ACxDH,SAAS,cAAc,OAAe,KAAqB;AACzD,KAAI,UAAU,GAAI,QAAO;AACzB,QAAO,MACJ,MAAM,IAAI,CACV,QAAQ,SAAS,SAAS,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,IAAI,CAC3D,KAAK,IAAI;;;;;;;;;;;;;;;;;AAkBd,SAAgB,uBAAuB,WAAmB,QAAwB;CAChF,IAAI;AACJ,KAAI;AACF,UAAQ,IAAI,IAAI,OAAO;SACjB;AACN,QAAM,IAAI,MAAM,iCAAiC,SAAS;;AAE5D,KAAI,MAAM,aAAa,OACrB,OAAM,IAAI,MAAM,2CAA2C,MAAM,SAAS,IAAI,OAAO,GAAG;CAG1F,MAAM,YAAY,UAAU,QAAQ,IAAI;CACxC,MAAM,OAAO,cAAc,KAAK,KAAK,UAAU,MAAM,UAAU;CAC/D,MAAM,aAAa,cAAc,KAAK,YAAY,UAAU,MAAM,GAAG,UAAU;CAE/E,MAAM,aAAa,WAAW,QAAQ,IAAI;CAC1C,MAAM,OAAO,eAAe,KAAK,aAAa,WAAW,MAAM,GAAG,WAAW;CAC7E,IAAI,QAAQ,eAAe,KAAK,KAAK,WAAW,MAAM,aAAa,EAAE;CAErE,MAAM,WAA0B,CAC9B,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,OAAO,CAClB;AACD,MAAK,MAAM,CAAC,QAAQ,SAClB,SAAQ,cAAc,OAAO,IAAI;AAEnC,MAAK,MAAM,CAAC,KAAK,UAAU,UAAU;EACnC,MAAM,OAAO,GAAG,IAAI,GAAG,mBAAmB,MAAM;AAChD,UAAQ,UAAU,KAAK,OAAO,GAAG,MAAM,GAAG;;AAG5C,QAAO,GAAG,KAAK,GAAG,QAAQ;;;;;AC/B5B,MAAa,yBAAyB;CACpC;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAIF,aAAa;GACX,MAAM;GACN,YAAY,EACV,YAAY;IACV,MAAM;IACN,aACE;IACH,EACF;GACD,UAAU,CAAC,aAAa;GACzB;EACF;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAEF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAGF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAEF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACF;AAID,MAAM,mBAAmB,IAAI,IAAY,uBAAuB,KAAK,MAAM,EAAE,KAAK,CAAC;AAEnF,SAAgB,gBAAgB,MAAqC;AACnE,QAAO,iBAAiB,IAAI,KAAK;;;AA0BnC,SAAS,mBAAmB,KAA8B;AACxD,KAAI,IAAI,UAAU,KAAA,GAAW;AAC3B,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO,IAAI;AAC9C,MAAI;AACF,UAAO,KAAK,UAAU,IAAI,MAAM;UAC1B;AACN,UAAO,OAAO,IAAI,MAAM;;;AAG5B,KAAI,IAAI,gBAAgB,KAAA,EAAW,QAAO,IAAI;AAC9C,KAAI,IAAI,cAAc,KAAA,EAAW,QAAO,IAAI;AAC5C,QAAO,IAAI,WAAW,IAAI;;AAG5B,SAAgB,wBAAwB,OAA8C;CACpF,MAAM,OAAO,MAAM,KAAK,IAAI,mBAAmB;AAC/C,QAAO;EACL,OAAO,MAAM;EACb,MAAM,KAAK,KAAK,IAAI;EACpB,WAAW,MAAM;EACjB;EACD;;AAGH,SAAgB,oBAAoB,YAA6C;AAC/E,QAAO,WACJ,kBAAkB,2BAA2B,CAC7C,KAAK,UAAU,wBAAwB,MAAM,CAAC;;AAGnD,SAAgB,oBAAoB,YAA6C;CAC/E,MAAM,WAAW,WAAW,kBAAkB,4BAA4B;CAC1E,MAAM,YAAY,WAAW,kBAAkB,2BAA2B;CAE1E,MAAM,sCAAsB,IAAI,KAA2C;AAC3E,MAAK,MAAM,YAAY,UACrB,qBAAoB,IAAI,SAAS,WAAW,SAAS;AAGvD,QAAO,SAAS,KAAK,YAA2C;EAC9D,MAAM,WAAW,oBAAoB,IAAI,QAAQ,UAAU;AAC3D,SAAO;GACL,WAAW,QAAQ;GACnB,KAAK,QAAQ,QAAQ;GACrB,QAAQ,QAAQ,QAAQ;GACxB,QAAQ,WAAW,SAAS,SAAS,SAAS;GAC9C,YAAY,WAAW,SAAS,SAAS,aAAa;GACtD,WAAW,QAAQ;GACnB,SAAS,WAAW,SAAS,YAAY;GAC1C;GACD;;AASJ,SAAgB,UAAU,YAA2B,QAAuC;AAC1F,QAAO;EAAE,OAAO,WAAW,aAAa;EAAE;EAAQ;;;;;;;AAgBpD,SAAgB,eAAe,WAAmB,QAA4C;AAC5F,KAAI,CAAC,OAAO,MAAM,OAAO,WAAW,KAClC,OAAM,IAAI,MACR,qGAED;AAEH,QAAO;EACL,WAAW,uBAAuB,WAAW,OAAO,OAAO;EAC3D,UAAU,OAAO;EAClB;;;AAQH,SAAgB,eAAe,YAA0D;AAGvF,QAAO,WAAW,KAAK,mBAAmB;EAAE,OAAO;EAAI,QAAQ;EAAM,CAAC;;;AAIxE,SAAgB,aAAa,YAAuD;AAClF,QAAO,WAAW,KAAK,+BAA+B,EAAE,CAAC;;;AAa3D,eAAsB,eAAe,YAAsD;CACzF,MAAM,EAAE,SAAS,MAAM,WAAW,KAAK,0BAA0B,EAAE,QAAQ,OAAO,CAAC;AACnF,QAAO;EAAE;EAAM,SAAS,yBAAyB;EAAQ,UAAU;EAAa;;;AAQlF,MAAM,iBAAiB,IAAI,IAAY;CACrC;CACA;CACA;CACD,CAAC;;AAGF,SAAgB,cAAc,MAAuB;AACnD,QAAO,eAAe,IAAI,KAAK;;;AAIjC,SAAgB,kBAAkB,QAA+C;AAC/E,QAAO,OAAO,IAAI,wBAAwB;;;AAI5C,SAAgB,aAAa,QAA0C;AACrE,QAAO,OAAO,IAAI,mBAAmB;;;AAIvC,SAAgB,0BAA0B,QAAuD;AAC/F,QAAO,OAAO,IAAI,gCAAgC;;;;;;;;;;;;;;;;;AC9SpD,SAAgB,sBAA8B;AAC5C,QAAO,YAAY,GAAG,CAAC,SAAS,MAAM;;;AAYxC,eAAe,uBAAsC;CACnD,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,KAAI,CAAC,WAAW,IAAI,CAClB,OAAM,QAAQ,IAAI;;;;;;AAQtB,eAAsB,iBAAiB,WAAyC;AAC9E,OAAM,sBAAsB;CAE5B,MAAM,SAAS,OAAO,MAAM,oBAAoB,YAAY;CAE5D,MAAM,MAAM,MAAM,IAAI,SAAiB,SAAS,WAAW;EACzD,MAAM,SAAS,aAAqB;AAClC,YAAS;AACT,WAAQ,SAAS;;EAEnB,MAAM,WAAW,QAAe;AAC9B,YAAS;AACT,UAAO,IAAI;;EAEb,MAAM,UAAU,SAAwB;AACtC,YAAS;AACT,0BAAO,IAAI,MAAM,mDAAmD,KAAK,GAAG,CAAC;;EAE/E,MAAM,gBAAgB;AACpB,UAAO,IAAI,OAAO,MAAM;AACxB,UAAO,IAAI,SAAS,QAAQ;AAC5B,UAAO,IAAI,QAAQ,OAAO;;AAE5B,SAAO,KAAK,OAAO,MAAM;AACzB,SAAO,KAAK,SAAS,QAAQ;AAC7B,SAAO,KAAK,QAAQ,OAAO;GAC3B;AAEF,QAAO;EACL;EACA,QAAQ,IAAI,QAAQ,UAAU,MAAM;EACpC,YAAY;AACV,UAAO,MAAM;;EAEhB;;;AASH,eAAsB,mBAAmB,OAA2C;CAElF,MAAM,UAAU,GAAG,MAAM,OAAO,SAAS,MAAM;CAC/C,MAAM,KAAK,MAAM,IAAI,SAAiB,YAAY;AAChD,SAAO,SAAS,SAAS,EAAE,OAAO,MAAM,GAAG,aAAa,QAAQ,SAAS,CAAC;GAC1E;AACF,QAAO;EACL;EACA;EACA;EACA,oBAAoB,MAAM;EAC1B,oBAAoB,MAAM;EAC1B;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;AAId,eAAsB,kBAAkB,OAAyC;CAC/E,MAAM,SAAS,MAAM,mBAAmB,MAAM;AAC9C,SAAQ,OAAO,MAAM,GAAG,OAAO,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5CrC,SAAgB,kBAAkB,MAA+B;CAC/D,MAAM,EAAE,YAAY,WAAW,oBAAoB;CAEnD,MAAM,SAAS,IAAI,OACjB;EAAE,MAAM;EAAa,SAAA;EAAsB,EAC3C,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,EAAE,CAChC;AAED,QAAO,kBAAkB,+BAA+B,EACtD,OAAO,uBAAuB,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE,EAC3D,EAAE;AAEH,QAAO,kBAAkB,uBAAuB,OAAO,YAAY;EACjE,MAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,CAAC,gBAAgB,KAAK,CACxB,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB;IAAQ,CAAC;GAC1D,SAAS;GACV;AAMH,MAAI,cAAc,KAAK,CACrB,KAAI;AACF,SAAM,WAAW,eAAe;AAChC,WAAQ,MAAR;IACE,KAAK,wBACH,QAAOC,aAAW,MAAM,kBAAkB,UAAU,CAAC;IACvD,KAAK,mBACH,QAAOA,aAAW,MAAM,aAAa,UAAU,CAAC;IAClD,KAAK,gCACH,QAAOA,aAAW,MAAM,0BAA0B,UAAU,CAAC;IAC/D,QACE,QAAO,YAAY,KAAK;;WAErB,KAAK;AACZ,UAAO,YAAY,KAAK,KAAK;;AAMjC,MAAI,SAAS,oBAAoB;GAC/B,MAAM,YAAY,QAAQ,OAAO,WAAW;AAC5C,OAAI,OAAO,cAAc,YAAY,cAAc,GACjD,QAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAqD,CAAC;IACtF,SAAS;IACV;AAEH,OAAI;AACF,WAAOA,aAAW,eAAe,WAAW,iBAAiB,CAAC,CAAC;YACxD,KAAK;AACZ,WAAO,YAAY,KAAK,KAAK;;;AAIjC,MAAI;AAGF,SAAM,WAAW,eAAe;WACzB,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,OAAI,SAAS,aAEX,QAAOA,aAAW,UAAU,YAAY,iBAAiB,CAAC,CAAC;AAE7D,UAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MAAM,GAAG,QAAQ;KAClB,CACF;IACD,SAAS;IACV;;AAGH,MAAI;AACF,WAAQ,MAAR;IACE,KAAK,wBACH,QAAOA,aAAW,oBAAoB,WAAW,CAAC;IACpD,KAAK,wBACH,QAAOA,aAAW,oBAAoB,WAAW,CAAC;IACpD,KAAK,aACH,QAAOA,aAAW,UAAU,YAAY,iBAAiB,CAAC,CAAC;IAC7D,KAAK,mBACH,QAAOA,aAAW,MAAM,eAAe,WAAW,CAAC;IACrD,KAAK,gBACH,QAAOA,aAAW,MAAM,aAAa,WAAW,CAAC;IACnD,KAAK,mBAAmB;KACtB,MAAM,OAAO,MAAM,eAAe,WAAW;AAC7C,YAAO,EACL,SAAS,CAAC;MAAE,MAAM;MAAkB,MAAM,KAAK;MAAM,UAAU,KAAK;MAAU,CAAC,EAChF;;IAEH,QACE,QAAO,YAAY,KAAK;;WAErB,KAAK;AACZ,UAAO,YAAY,KAAK,KAAK;;GAE/B;AAEF,QAAO;;AAGT,SAASA,aAAW,OAAgB;AAClC,QAAO,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,OAAO,MAAM,EAAE;EAAE,CAAC,EAAE;;AAGvF,SAAS,YAAY,MAAc;AACjC,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,iBAAiB;GAAQ,CAAC;EAAE,SAAS;EAAM;;AAG/F,SAAS,YAAY,KAAc,MAAc;AAE/C,QAAO;EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,GAAG,KAAK,WALJ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAKzB;GAClC,CACF;EACD,SAAS;EACV;;;;;;;;;AAeH,eAAsB,eAAe,UAAiC,EAAE,EAAiB;CACvF,MAAM,YAAY,QAAQ,aAAa;CAEvC,MAAM,QAAQ,MAAM,eAAe,EAAE,MAAM,WAAW,CAAC;CAEvD,IAAI,SAA6B;CACjC,IAAI,eAA6B;EAAE,IAAI;EAAO,QAAQ;EAAM;CAC5D,MAAM,QAAQ,qBAAqB;AAEnC,KAAI;AACF,WAAS,MAAM,iBAAiB,UAAU;AAC1C,iBAAe;GAAE,IAAI;GAAM,QAAQ,OAAO;GAAQ;AAClD,QAAM,kBAAkB;GAAE,QAAQ,OAAO;GAAQ;GAAO,CAAC;UAClD,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAQ,OAAO,MACb,wDAAwD,QAAQ;EAEjE;;CAGH,MAAM,aAAa,IAAI,kBAAkB,EAAE,cAAc,MAAM,SAAS,CAAC;CAGzE,MAAM,SAAS,kBAAkB;EAC/B;EACA,WAHgB,IAAI,cAAc,WAAW;EAI7C,uBAAuB;EACxB,CAAC;CAEF,MAAM,YAAY,IAAI,sBAAsB;CAE5C,MAAM,iBAAiB;AACrB,aAAW,OAAO;AAClB,UAAQ,MAAM;AACT,QAAM,OAAO;AACb,SAAO,OAAO;;AAErB,SAAQ,KAAK,UAAU,SAAS;AAChC,SAAQ,KAAK,WAAW,SAAS;AAEjC,OAAM,OAAO,QAAQ,UAAU;;;;AC9MjC,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,IAAa,gBAAb,MAAgD;CAC9C;CACA;CAEA,YAAY,SAA+B;AACzC,OAAK,gBAAgB,QAAQ;AAC7B,OAAK,YAAY,QAAQ,eAAe,QAAQ,MAAM,IAAI;;CAG5D,MAAc,aAAoC;EAChD,MAAM,MAAM,MAAM,KAAK,UAAU,KAAK,cAAc;AACpD,MAAI,CAAC,IAAI,GACP,OAAM,IAAI,MACR,mCAAmC,KAAK,cAAc,SAAS,IAAI,OAAO,GAAG,IAAI,WAAW,kGAE7F;EAEH,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,SAAO,SAAS,KAAK,GAAG,OAAO,EAAE;;CAGnC,MAAM,IAA6B,QAAqC;AACtE,UAAQ,QAAR;GACE,KAAK,mBAEH,QADc,MAAM,KAAK,YAAY;GAGvC,KAAK,iCAAiC;IACpC,MAAM,QAAQ,MAAM,KAAK,YAAY;AAIrC,WAD0C;KAAE,aAFxB,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc;KAEvB,YADtC,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa;KACR;;GAGvE,KAAK,wBAGH,QADkC,EAAE,OAAO,EAAE,EAAE;GAGjD,QACE,OAAM,IAAI,MAAM,uBAAuB,OAAO,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrChE,MAAM,uBAAuB;CAC3B;EACE,MAAM;EACN,aACE;EAIF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAEF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAEF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACD;EACE,MAAM;EACN,aACE;EAEF,aAAa;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE,UAAU,EAAE;GAAE;EAC9D;CACF;AAED,MAAM,iBAAiB,IAAI,IAAY,qBAAqB,KAAK,MAAM,EAAE,KAAK,CAAC;;AAQ/E,SAAgB,gBAAgB,OAA4B,EAAE,EAAU;CAEtE,MAAM,gBAAgB,GADF,QAAQ,IAAI,oBAAoB,wBACf;CACrC,MAAM,YAAY,KAAK,aAAa,IAAI,cAAc,EAAE,eAAe,CAAC;CAExE,MAAM,SAAS,IAAI,OACjB;EAAE,MAAM;EAAgB,SAAA;EAAsB,EAC9C,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,EAAE,CAChC;AAED,QAAO,kBAAkB,+BAA+B,EACtD,OAAO,qBAAqB,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE,EACzD,EAAE;AAEH,QAAO,kBAAkB,uBAAuB,OAAO,YAAY;EACjE,MAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,CAAC,eAAe,IAAI,KAAK,CAC3B,QAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB;IAAQ,CAAC;GAAE,SAAS;GAAM;AAGtF,MAAI;GAEF,MAAM,YAAY,SAAS,4BAA4B,qBAAqB;AAC5E,OAAI,CAAC,cAAc,UAAU,CAC3B,QAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,iBAAiB;KAAQ,CAAC;IAAE,SAAS;IAAM;AAEtF,WAAQ,WAAR;IACE,KAAK,mBACH,QAAO,WAAW,MAAM,aAAa,UAAU,CAAC;IAClD,KAAK,gCACH,QAAO,WAAW,MAAM,0BAA0B,UAAU,CAAC;IAC/D,KAAK,wBACH,QAAO,WAAW,MAAM,kBAAkB,UAAU,CAAC;IACvD,QACE,QAAO;KAAE,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,iBAAiB;MAAQ,CAAC;KAAE,SAAS;KAAM;;WAEjF,KAAK;AAEZ,UAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MACE,GANQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAM7C;KAGd,CACF;IACD,SAAS;IACV;;GAEH;AAEF,QAAO;;AAGT,SAAS,WAAW,OAAgB;AAClC,QAAO,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,OAAO,MAAM,EAAE;EAAE,CAAC,EAAE;;;AAIvF,eAAsB,eAA8B;CAClD,MAAM,SAAS,iBAAiB;CAChC,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;;;;;;;;;;;;;;;;;ACrIjC,SAAgB,UAAU,MAA+B;AACvD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;AACjB,MAAI,QAAQ,KAAA,EAAW;AACvB,MAAI,IAAI,WAAW,UAAU,CAC3B,QAAO,cAAc,IAAI,MAAM,EAAiB,CAAC;AAEnD,MAAI,QAAQ,UAAU;GACpB,MAAM,OAAO,KAAK,IAAI;AACtB,OAAI,SAAS,KAAA,EACX,OAAM,IAAI,MAAM,uDAAuD;AAEzE,UAAO,cAAc,KAAK;;;AAG9B,QAAO;;AAGT,SAAS,cAAc,OAAqB;AAC1C,KAAI,UAAU,MAAO,QAAO;AAC5B,KAAI,UAAU,QAAS,QAAO;AAC9B,OAAM,IAAI,MAAM,mBAAmB,MAAM,yCAAyC;;AAGpF,eAAe,OAAsB;AAEnC,KADa,UAAU,QAAQ,KAAK,MAAM,EAAE,CAAC,KAChC,MACX,OAAM,cAAc;KAEpB,OAAM,gBAAgB;;;AAK1B,SAAS,eAAwB;CAC/B,MAAM,QAAQ,KAAK;AACnB,KAAI,UAAU,KAAA,EAAW,QAAO;AAChC,KAAI;AACF,SAAO,cAAc,OAAO,KAAK,IAAI,KAAK;SACpC;AACN,SAAO;;;AAIX,IAAI,cAAc,CAChB,OAAM,CAAC,OAAO,QAAiB;CAC7B,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAQ,OAAO,MAAM,yBAAyB,QAAQ,IAAI;AAC1D,SAAQ,WAAW;EACnB"}
1
+ {"version":3,"file":"cli.js","names":["isObject","isObject","jsonResult"],"sources":["../../src/mcp/ait-chii-source.ts","../../src/mcp/chii-connection.ts","../../src/mcp/chii-relay.ts","../../src/mcp/deeplink.ts","../../src/mcp/tools.ts","../../src/mcp/totp.ts","../../src/mcp/tunnel.ts","../../src/mcp/debug-server.ts","../../src/mcp/ait-http-source.ts","../../src/mcp/server.ts","../../src/mcp/cli.ts"],"sourcesContent":["/**\n * Debug-mode `AitSource` — forwards `AIT.*` methods over the Chii channel.\n *\n * The AIT domain (`AIT.getSdkCallHistory` / `getMockState` /\n * `getOperationalEnvironment`) is non-standard CDP: the in-app side registers a\n * handler for these methods and answers them over the same Chii websocket the\n * CDP commands use. Building the AIT source on `ChiiCdpConnection.sendCommand`\n * means both domains share one transport (spec: \"the same MCP server forwards\n * both CDP and AIT domains\").\n *\n * The in-app `AIT.*` handler lives downstream in sdk-example. Here we build\n * the MCP-server-side forwarding + the injectable seam; tests inject a fake\n * `AitSource` returning canned responses, so this forwarding layer needs no\n * phone.\n *\n * Node-only (wraps the relay websocket connection).\n */\n\nimport type {\n AitMethodMap,\n AitMethodName,\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\n\n/** The slice of `ChiiCdpConnection` this source needs (keeps it testable). */\nexport interface AitCommandSender {\n sendCommand(method: string, params?: Record<string, unknown>): Promise<unknown>;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** Narrows an `AIT.getSdkCallHistory` response, tolerating a missing array. */\nfunction asSdkCallHistory(raw: unknown): AitSdkCallHistory {\n if (isObject(raw) && Array.isArray(raw.calls)) {\n return { calls: raw.calls as AitSdkCallHistory['calls'] };\n }\n return { calls: [] };\n}\n\n/** Narrows an `AIT.getMockState` response to an opaque record. */\nfunction asMockState(raw: unknown): AitMockState {\n return isObject(raw) ? raw : {};\n}\n\n/** Narrows an `AIT.getOperationalEnvironment` response. */\nfunction asOperationalEnvironment(raw: unknown): AitOperationalEnvironment {\n const environment =\n isObject(raw) && typeof raw.environment === 'string' ? raw.environment : 'unknown';\n const sdkVersion = isObject(raw) && typeof raw.sdkVersion === 'string' ? raw.sdkVersion : null;\n return { environment, sdkVersion };\n}\n\nexport class ChiiAitSource implements AitSource {\n constructor(private readonly sender: AitCommandSender) {}\n\n async get<M extends AitMethodName>(method: M): Promise<AitMethodMap[M]> {\n const raw = await this.sender.sendCommand(method);\n // The map's value type is resolved per-key below; the cast is the single\n // narrowing point (each branch returns the precise shape for `method`).\n switch (method) {\n case 'AIT.getSdkCallHistory':\n return asSdkCallHistory(raw) as AitMethodMap[M];\n case 'AIT.getMockState':\n return asMockState(raw) as AitMethodMap[M];\n case 'AIT.getOperationalEnvironment':\n return asOperationalEnvironment(raw) as AitMethodMap[M];\n default:\n throw new Error(`Unknown AIT method: ${String(method)}`);\n }\n }\n}\n","/**\n * Production `CdpConnection` backed by the local Chii relay.\n *\n * Topology (debug mode):\n * phone target.js --WS--> Chii relay :9100 <--WS-- this connection\n *\n * The phone connects to the relay as a `target`; this module connects as a\n * `client` (the role a CDP frontend would take) so CDP events the page emits\n * (`Runtime.consoleAPICalled`, `Network.*`) flow back here. We buffer recent\n * events in ring buffers the tool layer reads via `getBufferedEvents`.\n *\n * Node-only: imports `ws`. Never bundled into the browser/in-app entries.\n */\n\nimport { EventEmitter } from 'node:events';\nimport { WebSocket } from 'ws';\nimport type {\n CdpCommandMap,\n CdpCommandName,\n CdpConnection,\n CdpEventMap,\n CdpEventName,\n CdpTarget,\n} from './cdp-connection.js';\n\n/** Max events retained per domain ring buffer. */\nconst DEFAULT_BUFFER_SIZE = 500;\n\n/** A CDP message arriving over the relay websocket. */\ninterface CdpInboundMessage {\n id?: number;\n method?: string;\n params?: unknown;\n result?: unknown;\n error?: { message: string };\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction parseInbound(raw: string): CdpInboundMessage | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isObject(parsed)) return null;\n const message: CdpInboundMessage = {};\n if (typeof parsed.id === 'number') message.id = parsed.id;\n if (typeof parsed.method === 'string') message.method = parsed.method;\n if ('params' in parsed) message.params = parsed.params;\n if ('result' in parsed) message.result = parsed.result;\n if (isObject(parsed.error) && typeof parsed.error.message === 'string') {\n message.error = { message: parsed.error.message };\n }\n return message;\n}\n\nconst PHASE_1_EVENTS: readonly CdpEventName[] = [\n 'Runtime.consoleAPICalled',\n 'Network.requestWillBeSent',\n 'Network.responseReceived',\n];\n\nexport interface ChiiCdpConnectionOptions {\n /** Base URL of the local Chii relay HTTP/WS server, e.g. `http://127.0.0.1:9100`. */\n relayBaseUrl: string;\n /** Per-domain ring buffer size. */\n bufferSize?: number;\n}\n\n/**\n * Production CDP connection. Polls the relay for the first attached target,\n * opens a client websocket to it, enables Phase 1 domains, and buffers events.\n */\nexport class ChiiCdpConnection implements CdpConnection {\n private readonly relayBaseUrl: string;\n private readonly bufferSize: number;\n private readonly emitter = new EventEmitter();\n private readonly buffers = new Map<CdpEventName, unknown[]>();\n private readonly targets = new Map<string, CdpTarget>();\n\n private ws: WebSocket | null = null;\n private nextCommandId = 1;\n /** In-flight enableDomains() promise — concurrent callers share it. */\n private enablingPromise: Promise<void> | null = null;\n /** Pending request→response commands keyed by CDP message id. */\n private readonly pending = new Map<\n number,\n { resolve: (result: unknown) => void; reject: (err: Error) => void }\n >();\n\n constructor(options: ChiiCdpConnectionOptions) {\n this.relayBaseUrl = options.relayBaseUrl.replace(/\\/$/, '');\n this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE;\n for (const event of PHASE_1_EVENTS) this.buffers.set(event, []);\n // EventEmitter caps listeners at 10 by default; the tool layer may add\n // several short-lived subscriptions, so lift the cap.\n this.emitter.setMaxListeners(0);\n }\n\n /** Refresh the attached-target list from the relay's `GET /targets`. */\n async refreshTargets(): Promise<CdpTarget[]> {\n const res = await fetch(`${this.relayBaseUrl}/targets`);\n if (!res.ok) {\n throw new Error(`Chii relay /targets returned HTTP ${res.status} ${res.statusText}`);\n }\n const body: unknown = await res.json();\n const list = isObject(body) && Array.isArray(body.targets) ? body.targets : [];\n this.targets.clear();\n for (const item of list) {\n if (!isObject(item) || typeof item.id !== 'string') continue;\n this.targets.set(item.id, {\n id: item.id,\n title: typeof item.title === 'string' ? item.title : '',\n url: typeof item.url === 'string' ? item.url : '',\n });\n }\n return [...this.targets.values()];\n }\n\n listTargets(): CdpTarget[] {\n return [...this.targets.values()];\n }\n\n /**\n * Connect a client websocket to the first attached target and enable Phase 1\n * domains. Resolves once the socket is open and enable commands are sent.\n */\n async enableDomains(): Promise<void> {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) return;\n // If a connect attempt is already in-flight, await it rather than racing\n // to open a second websocket that would overwrite `this.ws` and leak the first.\n if (this.enablingPromise) return this.enablingPromise;\n this.enablingPromise = this._doEnableDomains().finally(() => {\n this.enablingPromise = null;\n });\n return this.enablingPromise;\n }\n\n private async _doEnableDomains(): Promise<void> {\n const targets = await this.refreshTargets();\n const target = targets[0];\n if (!target) {\n throw new Error('No mini-app page attached to the Chii relay yet.');\n }\n\n const wsBase = this.relayBaseUrl.replace(/^http/, 'ws');\n const clientId = `devtools-mcp-${Date.now()}`;\n const ws = new WebSocket(\n `${wsBase}/client/${clientId}?target=${encodeURIComponent(target.id)}`,\n );\n this.ws = ws;\n\n await new Promise<void>((resolve, reject) => {\n ws.once('open', () => resolve());\n ws.once('error', (err: Error) => reject(err));\n });\n\n ws.on('message', (data: WebSocket.RawData) => this.handleMessage(data.toString()));\n\n this.sendFireAndForget('Runtime.enable');\n this.sendFireAndForget('Network.enable');\n // DOM/Page domains back the Phase 2 command tools; Chii answers their\n // request→response commands once enabled.\n this.sendFireAndForget('DOM.enable');\n this.sendFireAndForget('Page.enable');\n }\n\n /** Fire-and-forget CDP message (used for `*.enable`, no result awaited). */\n private sendFireAndForget(method: string, params: Record<string, unknown> = {}): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n const id = this.nextCommandId++;\n this.ws.send(JSON.stringify({ id, method, params }));\n }\n\n /**\n * Issue a CDP command and resolve with its result (Phase 2). Rejects on a CDP\n * error frame or when no websocket is open (no page attached yet).\n */\n send<M extends CdpCommandName>(\n method: M,\n params?: CdpCommandMap[M]['params'],\n ): Promise<CdpCommandMap[M]['result']> {\n return this.sendCommand(method, (params ?? {}) as Record<string, unknown>) as Promise<\n CdpCommandMap[M]['result']\n >;\n }\n\n /**\n * Issue an arbitrary request→response command over the relay and resolve with\n * its raw result. Both the typed CDP {@link send} and the AIT domain (Phase 3\n * `AIT.*` methods, forwarded over the same Chii channel) build on this.\n */\n sendCommand(method: string, params: Record<string, unknown> = {}): Promise<unknown> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return Promise.reject(\n new Error('No mini-app page attached to the Chii relay yet. Call enableDomains() first.'),\n );\n }\n const id = this.nextCommandId++;\n const ws = this.ws;\n return new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n ws.send(JSON.stringify({ id, method, params }));\n });\n }\n\n private handleMessage(raw: string): void {\n const message = parseInbound(raw);\n if (!message) return;\n\n // Command response (has an id matching a pending request).\n if (typeof message.id === 'number' && this.pending.has(message.id)) {\n const waiter = this.pending.get(message.id);\n this.pending.delete(message.id);\n if (waiter) {\n if (message.error) waiter.reject(new Error(message.error.message));\n else waiter.resolve(message.result);\n }\n return;\n }\n\n // Event (buffered for the Phase 1 stream tools).\n if (typeof message.method !== 'string') return;\n if (!this.buffers.has(message.method as CdpEventName)) return;\n const event = message.method as CdpEventName;\n const buffer = this.buffers.get(event);\n if (!buffer) return;\n buffer.push(message.params);\n if (buffer.length > this.bufferSize) buffer.shift();\n this.emitter.emit(event, message.params);\n }\n\n getBufferedEvents<E extends CdpEventName>(event: E): ReadonlyArray<CdpEventMap[E]> {\n const buffer = this.buffers.get(event);\n return (buffer ?? []) as ReadonlyArray<CdpEventMap[E]>;\n }\n\n on<E extends CdpEventName>(event: E, listener: (payload: CdpEventMap[E]) => void): () => void {\n this.emitter.on(event, listener as (payload: unknown) => void);\n return () => this.emitter.off(event, listener as (payload: unknown) => void);\n }\n\n /** Close the relay client websocket and reject any in-flight commands. */\n close(): void {\n this.ws?.close();\n this.ws = null;\n for (const waiter of this.pending.values()) {\n waiter.reject(new Error('Chii relay connection closed.'));\n }\n this.pending.clear();\n }\n}\n","/**\n * Boots the local Chii relay server.\n *\n * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome\n * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.\n * The relay accepts a `target` websocket from the phone's injected `target.js`\n * and `client` websockets from CDP frontends (our MCP connection).\n *\n * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app\n * entries.\n *\n * TOTP auth (relay-side, authoritative gate):\n * When `verifyAuth` is provided, this module registers an HTTP upgrade\n * listener on the server BEFORE calling `chii.start({server})`. Node's\n * `http.Server` allows multiple 'upgrade' listeners; the first to call\n * `socket.destroy()` wins. Invalid auth → 401 + destroy (chii never sees\n * the connection). Valid auth → return without side-effect (chii handles it).\n *\n * Threat model: \"URL leak\" — someone obtains the tunnel URL (Slack paste, QR\n * screenshot, shoulder-surfing) but does not have the shared TOTP secret.\n * Rotating 6-digit code makes the URL stale after 30 s.\n * A determined attacker who extracts the secret from the dogfood bundle can\n * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).\n *\n * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear\n * in any log, error message, or process output. `verifyAuth` is a black-box\n * predicate from the caller's perspective; this module only forwards pass/fail.\n */\n\nimport { createServer, type IncomingMessage, type Server } from 'node:http';\nimport { createRequire } from 'node:module';\nimport type { Duplex } from 'node:stream';\n\nconst require = createRequire(import.meta.url);\n\n/** `chii/server` is CommonJS and shipped without TypeScript types. */\ninterface ChiiServerModule {\n start(options: {\n port?: number;\n host?: string;\n domain?: string;\n server?: Server;\n basePath?: string;\n }): Promise<void>;\n}\n\nfunction loadChiiServer(): ChiiServerModule {\n // `chii`'s package `main` is `./server/index.js`, exposing `{ start }`.\n const mod: unknown = require('chii');\n if (\n typeof mod === 'object' &&\n mod !== null &&\n 'start' in mod &&\n typeof (mod as { start: unknown }).start === 'function'\n ) {\n return mod as ChiiServerModule;\n }\n throw new Error('chii server module did not expose start()');\n}\n\nexport interface ChiiRelay {\n port: number;\n /** Base URL for the relay HTTP/WS server, e.g. `http://127.0.0.1:9100`. */\n baseUrl: string;\n close(): Promise<void>;\n}\n\nexport interface StartChiiRelayOptions {\n /** Local port for the relay. Default 9100. */\n port?: number;\n /** Bind host. Default 127.0.0.1 (tunnel reaches it locally). */\n host?: string;\n /**\n * Optional auth predicate for WebSocket upgrade requests.\n *\n * When provided, every inbound WebSocket upgrade is checked by calling\n * `verifyAuth(req)` before Chii processes it. Return `true` to allow the\n * upgrade; return `false` to reject with HTTP 401 and destroy the socket.\n *\n * The predicate MUST NOT log the secret or any TOTP code — it is a black-box\n * from this module's perspective.\n *\n * @param req - The raw HTTP `IncomingMessage` from the upgrade handshake.\n * Inspect `req.url` for query parameters (e.g. `at=<code>`).\n * @returns `true` if the upgrade is authorised, `false` to reject.\n */\n verifyAuth?: (req: IncomingMessage) => boolean;\n}\n\n/** Starts the Chii relay on the given port and resolves once listening. */\nexport async function startChiiRelay(options: StartChiiRelayOptions = {}): Promise<ChiiRelay> {\n const port = options.port ?? 9100;\n const host = options.host ?? '127.0.0.1';\n const { verifyAuth } = options;\n\n const httpServer = createServer();\n\n // Register our auth listener BEFORE chii.start() so it fires first.\n // Node's http.Server emits 'upgrade' to all listeners in registration order;\n // the first to destroy() the socket wins. Valid requests return without\n // side-effect so chii's own upgrade handler takes over normally.\n //\n // We only register when verifyAuth is provided so the no-auth path is\n // zero-overhead for tests and local-only dev sessions.\n if (verifyAuth) {\n httpServer.on('upgrade', (req: IncomingMessage, socket: Duplex) => {\n if (!verifyAuth(req)) {\n // Reject: send a minimal HTTP 401 response and close the socket.\n // We do NOT log req.url or any auth param here to avoid leaking codes.\n socket.write('HTTP/1.1 401 Unauthorized\\r\\nContent-Length: 0\\r\\n\\r\\n');\n socket.destroy();\n // Early return — chii's handler is NOT called for this socket.\n return;\n }\n // Auth passed: no-op. Chii's upgrade listener (registered below by\n // chii.start) will handle the rest.\n });\n }\n\n const chii = loadChiiServer();\n // Passing an existing `server` makes chii attach its Koa handler + WS upgrade\n // to our HTTP server rather than creating its own listener.\n await chii.start({ server: httpServer, domain: `${host}:${port}`, port });\n\n await new Promise<void>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(port, host, () => {\n httpServer.off('error', reject);\n resolve();\n });\n });\n\n return {\n port,\n baseUrl: `http://${host}:${port}`,\n close: () =>\n new Promise<void>((resolve) => {\n httpServer.close(() => resolve());\n }),\n };\n}\n","/**\n * Build a self-attaching dogfood deep link.\n *\n * `ait deploy --scheme-only` prints an `intoss-private://…?_deploymentId=<uuid>`\n * URL that opens a dogfood bundle on a phone. The in-app debug gate\n * (`src/in-app/gate.ts`) already auto-attaches when the entry URL also carries\n * `debug=1` and `relay=<wss-url>` — no QR scan or paste needed. This helper\n * splices those params (plus `at=<code>` when TOTP is enabled) into the scheme\n * URL so opening the result (e.g. via `adb shell am start -d \"<url>\"`) attaches\n * the running mini-app to the live Chii relay with zero human input.\n *\n * The Toss app propagates extra query params from the entry deep link into the\n * mini-app WebView's `location.search` (confirmed behavior), so the gate reads\n * them at attach time.\n *\n * TOTP `at=` param:\n * When a TOTP secret is active, `buildDeepLinkAttachUrl` accepts an optional\n * `totpCode` argument and splices `at=<code>` alongside `debug` and `relay`.\n * The code must be computed by the caller at call time — do NOT pre-compute\n * and cache it, because the 30-second window expires quickly. The in-app gate\n * (`src/in-app/gate.ts` Layer C) validates this code against the baked secret.\n *\n * Why not `URL`/`URLSearchParams`: `intoss-private:` is a non-special scheme.\n * The WHATWG `URL` parser treats such schemes opaquely (no host/path/query\n * decomposition you can rely on across runtimes), so query manipulation via\n * `url.searchParams` is not portable here. We splice the query string directly\n * on the raw string instead, which keeps the scheme, authority, path, and any\n * pre-existing params (notably `_deploymentId`) byte-for-byte intact.\n */\n\n/** A param the helper appends. Existing occurrences are replaced, not duplicated. */\ntype AppendParam = readonly [key: string, value: string];\n\nfunction stripExisting(query: string, key: string): string {\n if (query === '') return '';\n return query\n .split('&')\n .filter((pair) => pair !== '' && pair.split('=')[0] !== key)\n .join('&');\n}\n\n/**\n * Splices `debug=1`, `relay=<wssUrl>`, and (optionally) `at=<totpCode>` into a\n * scheme URL's query string, preserving everything else (scheme, authority,\n * path, hash, and the existing `_deploymentId` param). If any of the spliced\n * params is already present it is replaced so the helper is idempotent.\n *\n * @param schemeUrl - The `intoss-private://…?_deploymentId=<uuid>` URL printed\n * by `ait deploy --scheme-only`. Must already carry `_deploymentId` (Layer B\n * of the gate); this helper does not invent one.\n * @param wssUrl - The live relay URL (`wss://…trycloudflare.com`) from the\n * running debug MCP server's quick tunnel.\n * @param totpCode - Optional current TOTP code (6 digits). When provided, it\n * is spliced as `at=<totpCode>`. Must be computed at call time — it rotates\n * every 30 s. Pass `undefined` or omit when TOTP is disabled.\n * @returns The same URL with `debug=1&relay=<encoded wssUrl>[&at=<totpCode>]`\n * appended.\n * @throws If `wssUrl` is not a `wss:` URL (the gate rejects anything else, so\n * producing such a link would be a silent dead end).\n */\nexport function buildDeepLinkAttachUrl(\n schemeUrl: string,\n wssUrl: string,\n totpCode?: string,\n): string {\n let relay: URL;\n try {\n relay = new URL(wssUrl);\n } catch {\n throw new Error(`relay URL is not a valid URL: ${wssUrl}`);\n }\n if (relay.protocol !== 'wss:') {\n throw new Error(`relay URL must use the wss: scheme, got ${relay.protocol} (${wssUrl})`);\n }\n\n const hashIndex = schemeUrl.indexOf('#');\n const hash = hashIndex === -1 ? '' : schemeUrl.slice(hashIndex);\n const beforeHash = hashIndex === -1 ? schemeUrl : schemeUrl.slice(0, hashIndex);\n\n const queryIndex = beforeHash.indexOf('?');\n const base = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);\n let query = queryIndex === -1 ? '' : beforeHash.slice(queryIndex + 1);\n\n const appended: AppendParam[] = [\n ['debug', '1'],\n ['relay', wssUrl],\n ];\n // Only splice `at=` when a code is provided (TOTP enabled). Omitting it when\n // TOTP is disabled preserves backward compatibility with gate deployments\n // that do not yet evaluate the `at` param.\n if (totpCode !== undefined && totpCode !== '') {\n appended.push(['at', totpCode]);\n }\n\n // Always strip the `at` key from the existing query so a stale code from a\n // previous run is removed even when the caller does not provide a fresh code.\n query = stripExisting(query, 'at');\n\n for (const [key] of appended) {\n query = stripExisting(query, key);\n }\n for (const [key, value] of appended) {\n const pair = `${key}=${encodeURIComponent(value)}`;\n query = query === '' ? pair : `${query}&${pair}`;\n }\n\n return `${base}?${query}${hash}`;\n}\n","/**\n * Debug-mode MCP tools (Phase 1–3 + safe-area probe).\n *\n * Read-only tools that normalize CDP / AIT data into `chrome-devtools-mcp`-\n * compatible shapes. The tools never touch a websocket or HTTP endpoint\n * directly — they read from an injected `CdpConnection` (CDP events/commands)\n * or `AitSource` (AIT.* domain), which is what makes them unit-testable with a\n * fake. No phone and no running dev server are needed in tests.\n *\n * Phase 1 (CDP events):\n * - `list_console_messages` ← Runtime.consoleAPICalled\n * - `list_network_requests` ← Network.requestWillBeSent + responseReceived\n * - `list_pages` ← Chii relay target list + tunnel status\n * Phase 2 (CDP commands):\n * - `get_dom_document` ← DOM.getDocument\n * - `take_snapshot` ← DOMSnapshot.captureSnapshot\n * - `take_screenshot` ← Page.captureScreenshot\n * - `measure_safe_area` ← Runtime.evaluate (safe-area probe)\n * Phase 3 (AIT.* domain — CDP can't cover these):\n * - `AIT.getSdkCallHistory`\n * - `AIT.getMockState`\n * - `AIT.getOperationalEnvironment`\n */\n\nimport type {\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\nimport type {\n CdpConnection,\n CdpRemoteObject,\n ConsoleApiCalledEvent,\n DomGetDocumentResult,\n DomSnapshotResult,\n NetworkRequestWillBeSentEvent,\n NetworkResponseReceivedEvent,\n} from './cdp-connection.js';\nimport { buildDeepLinkAttachUrl } from './deeplink.js';\n\n/** Tunnel state surfaced by `list_pages`. */\nexport interface TunnelStatus {\n /** Whether the cloudflared quick tunnel is up. */\n up: boolean;\n /** Public `wss://*.trycloudflare.com` relay URL the phone attaches to. */\n wssUrl: string | null;\n}\n\n/** Static MCP tool descriptors (name + JSONSchema) for the full debug tool surface. */\nexport const DEBUG_TOOL_DEFINITIONS = [\n {\n name: 'list_console_messages',\n description:\n 'Lists recent console messages (console.log/warn/error/info) captured from the attached ' +\n 'mini-app page over CDP (Runtime.consoleAPICalled). Read-only. Returns level, text, ' +\n 'timestamp, and stringified args, oldest-first.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'list_network_requests',\n description:\n 'Lists recent network requests (XHR/fetch) captured from the attached mini-app page over ' +\n 'CDP (Network.requestWillBeSent + Network.responseReceived). Read-only. Returns url, ' +\n 'method, status, and timing, oldest-first.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'list_pages',\n description:\n 'Lists the mini-app page(s) the Chii relay currently sees attached, plus whether the ' +\n 'cloudflared tunnel is up and the public wss relay URL the phone uses to attach. ' +\n 'Call this first to confirm a page is attached before reading console/network.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'build_attach_url',\n description:\n 'Turns an `ait deploy --scheme-only` URL (intoss-private://…?_deploymentId=<uuid>) into a ' +\n 'self-attaching deep link by splicing in debug=1 and the live relay URL for this session. ' +\n 'Opening the result on the phone (e.g. `adb shell am start -d \"<url>\"`) attaches the mini-app ' +\n 'to this debug session with no QR scan. Requires the tunnel to be up — call list_pages first.',\n inputSchema: {\n type: 'object',\n properties: {\n scheme_url: {\n type: 'string',\n description:\n 'The intoss-private:// scheme URL from `ait deploy --scheme-only` (must carry _deploymentId).',\n },\n },\n required: ['scheme_url'],\n },\n },\n {\n name: 'get_dom_document',\n description:\n 'Returns the DOM tree of the attached mini-app page over CDP (DOM.getDocument). Read-only. ' +\n 'Use for structural/layout regression diagnosis (e.g. confirming an element exists, ' +\n 'inspecting attributes). Returns the document root node with children.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'take_snapshot',\n description:\n 'Captures a serialized snapshot of the attached page over CDP (DOMSnapshot.captureSnapshot). ' +\n 'Read-only. Returns the documents + interned strings table for visual-regression diagnosis ' +\n '(e.g. checking computed CSS custom properties like --sat against the live layout).',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'take_screenshot',\n description:\n 'Captures a PNG screenshot of the attached mini-app page over CDP (Page.captureScreenshot) ' +\n 'so the agent can see the phone screen directly. Read-only. Returns an image content block.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'measure_safe_area',\n description:\n 'Runs a safe-area probe on the attached mini-app page via Runtime.evaluate and returns ' +\n 'normalized safe-area insets, viewport geometry, device pixel ratio, and User-Agent. ' +\n 'Read-only — does not modify page state. ' +\n 'Use in a relay session (phone attached) to get ground-truth values for upgrading a ' +\n 'viewport preset from extrapolated/placeholder to measured. ' +\n 'Requires the relay to be attached — call list_pages first.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getSdkCallHistory',\n description:\n 'Returns the recent Apps In Toss SDK call trace (method, args, result/error, timestamp) that ' +\n 'raw CDP cannot observe. Read-only. Use to confirm an SDK call fired and how it resolved ' +\n '(e.g. a saveBase64Data permission regression).',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getMockState',\n description:\n 'Returns the devtools mock state snapshot (window.__ait) — environment, permissions, location, ' +\n 'auth, network, IAP, and more. Read-only. In dev mode this is the live browser mock state; in ' +\n 'debug mode the in-app side reports it over the AIT domain.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getOperationalEnvironment',\n description:\n 'Returns getOperationalEnvironment() plus the resolved SDK version — metadata raw CDP cannot ' +\n 'observe. Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n] as const;\n\nexport type DebugToolName = (typeof DEBUG_TOOL_DEFINITIONS)[number]['name'];\n\nconst DEBUG_TOOL_NAMES = new Set<string>(DEBUG_TOOL_DEFINITIONS.map((t) => t.name));\n\nexport function isDebugToolName(name: string): name is DebugToolName {\n return DEBUG_TOOL_NAMES.has(name);\n}\n\n/** Normalized console message returned by `list_console_messages`. */\nexport interface ConsoleMessage {\n level: string;\n text: string;\n timestamp: number;\n args: string[];\n}\n\n/** Normalized network request returned by `list_network_requests`. */\nexport interface NetworkRequest {\n requestId: string;\n url: string;\n method: string;\n /** HTTP status once a response was seen, else null (still in-flight). */\n status: number | null;\n statusText: string | null;\n /** Request start (CDP timestamp). */\n startTime: number;\n /** Response received (CDP timestamp), else null. */\n endTime: number | null;\n}\n\n/** Renders a CDP `RemoteObject` console arg to a stable display string. */\nfunction renderRemoteObject(arg: CdpRemoteObject): string {\n if (arg.value !== undefined) {\n if (typeof arg.value === 'string') return arg.value;\n try {\n return JSON.stringify(arg.value);\n } catch {\n return String(arg.value);\n }\n }\n if (arg.description !== undefined) return arg.description;\n if (arg.className !== undefined) return arg.className;\n return arg.subtype ?? arg.type;\n}\n\nexport function normalizeConsoleMessage(event: ConsoleApiCalledEvent): ConsoleMessage {\n const args = event.args.map(renderRemoteObject);\n return {\n level: event.type,\n text: args.join(' '),\n timestamp: event.timestamp,\n args,\n };\n}\n\nexport function listConsoleMessages(connection: CdpConnection): ConsoleMessage[] {\n return connection\n .getBufferedEvents('Runtime.consoleAPICalled')\n .map((event) => normalizeConsoleMessage(event));\n}\n\nexport function listNetworkRequests(connection: CdpConnection): NetworkRequest[] {\n const requests = connection.getBufferedEvents('Network.requestWillBeSent');\n const responses = connection.getBufferedEvents('Network.responseReceived');\n\n const responseByRequestId = new Map<string, NetworkResponseReceivedEvent>();\n for (const response of responses) {\n responseByRequestId.set(response.requestId, response);\n }\n\n return requests.map((request: NetworkRequestWillBeSentEvent) => {\n const response = responseByRequestId.get(request.requestId);\n return {\n requestId: request.requestId,\n url: request.request.url,\n method: request.request.method,\n status: response ? response.response.status : null,\n statusText: response ? response.response.statusText : null,\n startTime: request.timestamp,\n endTime: response ? response.timestamp : null,\n };\n });\n}\n\n/** Result of `list_pages`: attach status + tunnel state. */\nexport interface ListPagesResult {\n pages: ReturnType<CdpConnection['listTargets']>;\n tunnel: TunnelStatus;\n}\n\nexport function listPages(connection: CdpConnection, tunnel: TunnelStatus): ListPagesResult {\n return { pages: connection.listTargets(), tunnel };\n}\n\n/** A `build_attach_url` result: the spliced deep link the phone should open. */\nexport interface BuildAttachUrlResult {\n /** The scheme URL with `debug=1&relay=<wss>` spliced in. */\n attachUrl: string;\n /** The relay URL that was spliced in (this session's quick tunnel). */\n relayUrl: string;\n}\n\n/**\n * Builds a self-attaching dogfood deep link from an `ait deploy --scheme-only`\n * URL plus this session's live relay. Throws if the tunnel is not up yet (no\n * relay URL to splice in) — the caller surfaces that as a tool error.\n */\nexport function buildAttachUrl(schemeUrl: string, tunnel: TunnelStatus): BuildAttachUrlResult {\n if (!tunnel.up || tunnel.wssUrl === null) {\n throw new Error(\n 'No relay URL yet — the cloudflared quick tunnel is not up. ' +\n 'Call list_pages to check tunnel status.',\n );\n }\n return {\n attachUrl: buildDeepLinkAttachUrl(schemeUrl, tunnel.wssUrl),\n relayUrl: tunnel.wssUrl,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 2 — DOM / snapshot / screenshot (CDP commands) */\n/* -------------------------------------------------------------------------- */\n\n/** Returns the DOM tree of the attached page (`DOM.getDocument`). */\nexport function getDomDocument(connection: CdpConnection): Promise<DomGetDocumentResult> {\n // `pierce: true` flattens shadow roots; depth -1 returns the whole subtree so\n // a single call yields the full tree for structural diagnosis.\n return connection.send('DOM.getDocument', { depth: -1, pierce: true });\n}\n\n/** Returns a serialized page snapshot (`DOMSnapshot.captureSnapshot`). */\nexport function takeSnapshot(connection: CdpConnection): Promise<DomSnapshotResult> {\n return connection.send('DOMSnapshot.captureSnapshot', {});\n}\n\n/** A `take_screenshot` result: the raw base64 PNG plus a ready-to-use data URI. */\nexport interface ScreenshotResult {\n /** Base64-encoded PNG bytes (no data-URI prefix). */\n data: string;\n /** `data:image/png;base64,…` form for clients that render a URI. */\n dataUri: string;\n mimeType: 'image/png';\n}\n\n/** Captures a PNG screenshot of the attached page (`Page.captureScreenshot`). */\nexport async function takeScreenshot(connection: CdpConnection): Promise<ScreenshotResult> {\n const { data } = await connection.send('Page.captureScreenshot', { format: 'png' });\n return { data, dataUri: `data:image/png;base64,${data}`, mimeType: 'image/png' };\n}\n\n/* -------------------------------------------------------------------------- */\n/* measure_safe_area — Runtime.evaluate probe */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The JS probe injected via `Runtime.evaluate`. It reads:\n * 1. `env(safe-area-inset-*)` via a temporary element with padding set to\n * those CSS env vars, then `getComputedStyle`.\n * 2. `SafeAreaInsets.get()` if the native SDK object is available.\n * 3. nav bar geometry (first `.ait-navbar` element height, if present).\n * 4. `innerWidth`, `innerHeight`, `devicePixelRatio`, `navigator.userAgent`.\n *\n * Returns a plain JSON-serialisable object so `returnByValue: true` works.\n *\n * NOTE: This expression is evaluated in the page context on the real device.\n * It does not mutate any page state — the temporary element is removed after\n * reading. No secret or auth token is read or returned.\n */\nexport const SAFE_AREA_PROBE_EXPRESSION = `\n(function() {\n var el = document.createElement('div');\n el.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;visibility:hidden;' +\n 'padding-top:env(safe-area-inset-top,0px);' +\n 'padding-right:env(safe-area-inset-right,0px);' +\n 'padding-bottom:env(safe-area-inset-bottom,0px);' +\n 'padding-left:env(safe-area-inset-left,0px)';\n document.documentElement.appendChild(el);\n var cs = window.getComputedStyle(el);\n var cssEnv = {\n top: parseFloat(cs.paddingTop) || 0,\n right: parseFloat(cs.paddingRight) || 0,\n bottom: parseFloat(cs.paddingBottom) || 0,\n left: parseFloat(cs.paddingLeft) || 0\n };\n document.documentElement.removeChild(el);\n var sdkInsets = null;\n try {\n if (typeof SafeAreaInsets !== 'undefined' && SafeAreaInsets && typeof SafeAreaInsets.get === 'function') {\n sdkInsets = SafeAreaInsets.get();\n }\n } catch(_) {}\n var navBarHeight = null;\n try {\n var nb = document.querySelector('.ait-navbar');\n if (nb) navBarHeight = nb.getBoundingClientRect().height;\n } catch(_) {}\n return JSON.stringify({\n cssEnv: cssEnv,\n sdkInsets: sdkInsets,\n navBarHeight: navBarHeight,\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio,\n userAgent: navigator.userAgent\n });\n})()\n`.trim();\n\n/**\n * Normalized result returned by `measure_safe_area`.\n *\n * All inset values are in CSS pixels as reported by the real device.\n * `userAgent` is included for device identification; it never contains\n * authentication secrets or session tokens.\n */\nexport interface SafeAreaMeasurement {\n /**\n * `env(safe-area-inset-*)` values read via `getComputedStyle` on the device.\n * On iOS inside the Toss host WebView this is typically all-zero because the\n * WebView viewport is placed below the physical notch by the host app.\n */\n cssEnv: { top: number; right: number; bottom: number; left: number };\n /**\n * `SafeAreaInsets.get()` result from the native SDK, if available.\n * In the Toss host this carries the nav bar height as `top` and the\n * home-indicator height as `bottom`. `null` when the SDK object is absent\n * (e.g. outside a Toss WebView).\n */\n sdkInsets: { top: number; right: number; bottom: number; left: number } | null;\n /**\n * Height of the `.ait-navbar` element (px) if present, else `null`.\n * Useful to cross-validate `sdkInsets.top` against the rendered nav bar.\n */\n navBarHeight: number | null;\n /** CSS viewport width (`window.innerWidth`). */\n innerWidth: number;\n /** CSS viewport height (`window.innerHeight`). */\n innerHeight: number;\n /**\n * Device pixel ratio (`window.devicePixelRatio`).\n * Note: `window.devicePixelRatio` is read-only in the browser, so devtools\n * cannot emulate DPR locally — this is the ground-truth value from the device.\n */\n devicePixelRatio: number;\n /**\n * `navigator.userAgent` string for device identification.\n * Does not contain authentication secrets.\n */\n userAgent: string;\n}\n\n/**\n * Parses a raw `Runtime.evaluate` result value into a `SafeAreaMeasurement`.\n * The probe returns a JSON string (because `returnByValue:true` with a plain\n * object works unreliably across Chii relay versions — stringifying is safer).\n *\n * Throws if the result is missing, contains an exception, or cannot be parsed.\n */\nexport function normalizeSafeAreaResult(rawValue: unknown): SafeAreaMeasurement {\n if (typeof rawValue !== 'string') {\n throw new Error(\n `measure_safe_area: probe returned unexpected type \"${typeof rawValue}\" — expected JSON string`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawValue);\n } catch {\n throw new Error(`measure_safe_area: probe returned non-JSON string: ${rawValue}`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('measure_safe_area: parsed result is not an object');\n }\n const obj = parsed as Record<string, unknown>;\n\n function requireInsets(\n key: string,\n ): { top: number; right: number; bottom: number; left: number } | null {\n const v = obj[key];\n if (v === null || v === undefined) return null;\n if (typeof v !== 'object') return null;\n const r = v as Record<string, unknown>;\n return {\n top: typeof r.top === 'number' ? r.top : 0,\n right: typeof r.right === 'number' ? r.right : 0,\n bottom: typeof r.bottom === 'number' ? r.bottom : 0,\n left: typeof r.left === 'number' ? r.left : 0,\n };\n }\n\n const cssEnv = requireInsets('cssEnv') ?? { top: 0, right: 0, bottom: 0, left: 0 };\n const sdkInsets = requireInsets('sdkInsets');\n const navBarHeight = typeof obj.navBarHeight === 'number' ? obj.navBarHeight : null;\n const innerWidth = typeof obj.innerWidth === 'number' ? obj.innerWidth : 0;\n const innerHeight = typeof obj.innerHeight === 'number' ? obj.innerHeight : 0;\n const devicePixelRatio = typeof obj.devicePixelRatio === 'number' ? obj.devicePixelRatio : 1;\n const userAgent = typeof obj.userAgent === 'string' ? obj.userAgent : '';\n\n return { cssEnv, sdkInsets, navBarHeight, innerWidth, innerHeight, devicePixelRatio, userAgent };\n}\n\n/**\n * Runs the safe-area probe on the attached page and returns a normalized\n * `SafeAreaMeasurement`. Read-only — does not mutate page state.\n *\n * Throws on CDP error, probe exception, or result parse failure.\n */\nexport async function measureSafeArea(connection: CdpConnection): Promise<SafeAreaMeasurement> {\n const result = await connection.send('Runtime.evaluate', {\n expression: SAFE_AREA_PROBE_EXPRESSION,\n returnByValue: true,\n awaitPromise: false,\n });\n if (result.exceptionDetails) {\n const msg =\n result.exceptionDetails.exception?.description ??\n result.exceptionDetails.text ??\n 'Runtime.evaluate threw an exception';\n throw new Error(`measure_safe_area: probe threw — ${msg}`);\n }\n return normalizeSafeAreaResult(result.result.value);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 3 — AIT.* domain (CDP can't cover these) */\n/* -------------------------------------------------------------------------- */\n\n/** Set of tool names served by the AIT source rather than the CDP connection. */\nconst AIT_TOOL_NAMES = new Set<string>([\n 'AIT.getSdkCallHistory',\n 'AIT.getMockState',\n 'AIT.getOperationalEnvironment',\n]);\n\n/** True for the Phase 3 AIT.* tools (served by an `AitSource`, not CDP). */\nexport function isAitToolName(name: string): boolean {\n return AIT_TOOL_NAMES.has(name);\n}\n\n/** Returns the recent SDK call trace (`AIT.getSdkCallHistory`). */\nexport function getSdkCallHistory(source: AitSource): Promise<AitSdkCallHistory> {\n return source.get('AIT.getSdkCallHistory');\n}\n\n/** Returns the devtools mock-state snapshot (`AIT.getMockState`). */\nexport function getMockState(source: AitSource): Promise<AitMockState> {\n return source.get('AIT.getMockState');\n}\n\n/** Returns the operational environment + SDK version (`AIT.getOperationalEnvironment`). */\nexport function getOperationalEnvironment(source: AitSource): Promise<AitOperationalEnvironment> {\n return source.get('AIT.getOperationalEnvironment');\n}\n","/**\n * RFC 6238 TOTP implementation (Node.js, node:crypto only).\n *\n * External TOTP libraries (otplib, speakeasy, …) are intentionally NOT used\n * to keep the dependency surface minimal. This hand-roll is ~30 lines and\n * covers exactly what relay-side auth needs.\n *\n * Algorithm summary (RFC 6238 + RFC 4226):\n * T = floor(now / 30) — 30-second time step counter\n * K = Buffer.from(secret, 'hex') — shared secret (raw bytes, hex-encoded)\n * MAC = HMAC-SHA1(K, T as 8-byte big-endian uint64)\n * offset = MAC[19] & 0x0f\n * code = (MAC[offset..offset+4] & 0x7fffffff) % 10^6 — 6 digits\n *\n * Security note (keep this comment accurate):\n * The baked-in secret in a dogfood build is extractable from the bundle by a\n * determined reverse engineer. This mechanism raises the bar from\n * \"anyone with the URL\" to \"URL + bundle extraction + live TOTP calculation\".\n * Casual URL leaks (Slack paste, QR screenshot, shoulder-surfing) are\n * blocked; deliberate reverse engineering is not. See threat model in\n * src/mcp/chii-relay.ts and umbrella CLAUDE.md §4.\n *\n * SECRET-HANDLING: secret values and computed codes MUST NOT appear in any\n * log, error message, or string visible outside this module. Only boolean\n * pass/fail and reason enum values are safe to surface.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** Time step window in seconds (RFC 6238 default). */\nconst TIME_STEP = 30;\n\n/** Number of digits in the generated code. */\nconst DIGITS = 6;\n\n/**\n * Derives a 6-digit TOTP code from a hex-encoded secret at the given wall-\n * clock time.\n *\n * @param secret - The shared secret as a hex string (e.g. 64 hex chars = 32\n * bytes). Must be the output of `generateAttachToken()` or compatible.\n * @param when - Unix timestamp in milliseconds. Defaults to `Date.now()`.\n * @returns A zero-padded 6-digit decimal string, e.g. `\"042193\"`.\n */\nexport function generateTotp(secret: string, when: number = Date.now()): string {\n const key = Buffer.from(secret, 'hex');\n // Clamp to 0 so negative timestamps (e.g. in ±skew checks near epoch) do not\n // produce a negative counter, which would cause writeUInt32BE to throw.\n const counter = Math.max(0, Math.floor(when / 1000 / TIME_STEP));\n\n // Encode counter as 8-byte big-endian unsigned integer.\n const counterBuf = Buffer.alloc(8);\n // JavaScript numbers are safe integers up to 2^53; counter is ~7.5×10^10 at\n // year 9999 — well within safe range so standard bitwise ops are fine.\n const hi = Math.floor(counter / 0x100000000);\n const lo = counter >>> 0;\n counterBuf.writeUInt32BE(hi, 0);\n counterBuf.writeUInt32BE(lo, 4);\n\n const mac = createHmac('sha1', key).update(counterBuf).digest();\n\n // Dynamic truncation (RFC 4226 §5.4).\n const offset = mac[19] & 0x0f;\n const binCode =\n ((mac[offset] & 0x7f) << 24) |\n ((mac[offset + 1] & 0xff) << 16) |\n ((mac[offset + 2] & 0xff) << 8) |\n (mac[offset + 3] & 0xff);\n\n const otp = binCode % 10 ** DIGITS;\n return otp.toString().padStart(DIGITS, '0');\n}\n\n/**\n * Verifies a TOTP code against the secret, accepting ±`skew` time steps to\n * tolerate clock drift between the relay host and the client device.\n *\n * Uses `timingSafeEqual` for constant-time comparison to prevent timing\n * side-channel attacks.\n *\n * @param secret - Hex-encoded shared secret.\n * @param code - The 6-digit code to verify (string or numeric).\n * @param when - Unix timestamp in milliseconds. Defaults to `Date.now()`.\n * @param skew - Number of adjacent steps to accept on either side. Default 1\n * (accepts T-1, T, T+1 — a 90-second acceptance window).\n * @returns `true` if the code matches any accepted step, `false` otherwise.\n */\nexport function verifyTotp(\n secret: string,\n code: string,\n when: number = Date.now(),\n skew: number = 1,\n): boolean {\n const normalised = String(code).padStart(DIGITS, '0');\n if (normalised.length !== DIGITS || !/^\\d{6}$/.test(normalised)) {\n return false;\n }\n\n const candidateBuf = Buffer.from(normalised, 'utf8');\n\n for (let delta = -skew; delta <= skew; delta++) {\n const stepWhen = when + delta * TIME_STEP * 1000;\n const expected = generateTotp(secret, stepWhen);\n const expectedBuf = Buffer.from(expected, 'utf8');\n if (timingSafeEqual(expectedBuf, candidateBuf)) {\n return true;\n }\n }\n\n return false;\n}\n","/**\n * cloudflared quick tunnel + attach banner for the debug-mode MCP server.\n *\n * On spawn, the debug server opens an accountless `*.trycloudflare.com` quick\n * tunnel to the local Chii relay so the phone can attach over a public wss URL,\n * then prints an ASCII QR + attach instructions. When TOTP auth is enabled\n * (`AIT_DEBUG_TOTP_SECRET` is set), the QR encodes only the base relay URL —\n * the TOTP code (`at=`) is NOT included because it rotates every 30 s and\n * would be stale by the time a human scans. The in-app deep-link builder\n * splices the live code at attach time.\n *\n * SECRET-HANDLING: The TOTP secret and computed code values MUST NOT appear\n * in any output from this module.\n *\n * Node-only: spawns the cloudflared binary and writes to stdout/stderr.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { bin, install, Tunnel } from 'cloudflared';\nimport qrcode from 'qrcode-terminal';\n\n/** Generates a 32-byte hex attach token shown as a pairing hint (relay-side validation is a later phase). */\nexport function generateAttachToken(): string {\n return randomBytes(32).toString('hex');\n}\n\nexport interface QuickTunnel {\n /** Public `https://*.trycloudflare.com` URL the tunnel exposes. */\n url: string;\n /** Same host as `wss://` — the relay endpoint the phone attaches to. */\n wssUrl: string;\n stop(): void;\n}\n\n/** Ensures the cloudflared binary is installed (downloads + caches on first run). */\nasync function ensureCloudflaredBin(): Promise<void> {\n const { existsSync } = await import('node:fs');\n if (!existsSync(bin)) {\n await install(bin);\n }\n}\n\n/**\n * Opens a cloudflared quick tunnel to the local relay port and resolves once\n * the public URL is assigned.\n */\nexport async function startQuickTunnel(localPort: number): Promise<QuickTunnel> {\n await ensureCloudflaredBin();\n\n const tunnel = Tunnel.quick(`http://127.0.0.1:${localPort}`);\n\n const url = await new Promise<string>((resolve, reject) => {\n const onUrl = (assigned: string) => {\n cleanup();\n resolve(assigned);\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const onExit = (code: number | null) => {\n cleanup();\n reject(new Error(`cloudflared exited before assigning a URL (code ${code})`));\n };\n const cleanup = () => {\n tunnel.off('url', onUrl);\n tunnel.off('error', onError);\n tunnel.off('exit', onExit);\n };\n tunnel.once('url', onUrl);\n tunnel.once('error', onError);\n tunnel.once('exit', onExit);\n });\n\n return {\n url,\n wssUrl: url.replace(/^https/, 'wss'),\n stop: () => {\n tunnel.stop();\n },\n };\n}\n\nexport interface AttachBannerInput {\n wssUrl: string;\n /**\n * Whether TOTP auth is enabled on the relay (`AIT_DEBUG_TOTP_SECRET` is set).\n *\n * When `true`, the banner notes that a rotating code (`at=`) will be\n * appended to attach URLs at call time — the code is NOT printed here\n * because it rotates every 30 s and would be stale in seconds.\n */\n totpEnabled: boolean;\n}\n\n/**\n * Renders the attach banner (relay URL + ASCII QR) as a string.\n *\n * The QR encodes the base `wssUrl` only. When `totpEnabled` is true, a note\n * is added that attach URLs generated by `build_attach_url` will include a\n * live TOTP code (`at=`) appended at call time.\n *\n * SECRET-HANDLING: no secret value, TOTP code, or intermediate value is\n * included in this output.\n */\nexport async function renderAttachBanner(input: AttachBannerInput): Promise<string> {\n // The QR encodes only the relay wssUrl — no token or code. This is safe\n // because the relay gate enforces the code at WS upgrade time anyway; the\n // QR is just for locating the relay, not for bypassing auth.\n const qr = await new Promise<string>((resolve) => {\n qrcode.generate(input.wssUrl, { small: true }, (rendered) => resolve(rendered));\n });\n\n const authNote = input.totpEnabled\n ? ' auth: TOTP enabled — attach URLs include a rotating code (at=).'\n : ' auth: none (set AIT_DEBUG_TOTP_SECRET to enable TOTP).';\n\n return [\n '',\n 'AIT debug — attach a mini-app to this session',\n '',\n ` relay (wss): ${input.wssUrl}`,\n authNote,\n '',\n ' Use build_attach_url to generate a deep link with the current TOTP code.',\n ' Scan the QR to locate the relay (open the dogfood URL separately with',\n ' ?debug=1&relay=<wss>&at=<code> or use the build_attach_url tool):',\n '',\n qr,\n ].join('\\n');\n}\n\n/** Prints the attach banner to stderr (stdout is the MCP stdio channel). */\nexport async function printAttachBanner(input: AttachBannerInput): Promise<void> {\n const banner = await renderAttachBanner(input);\n process.stderr.write(`${banner}\\n`);\n}\n","/**\n * @ait-co/devtools debug-mode MCP server (stdio).\n *\n * Lets an AI coding agent attach to a running mini-app (real Toss WebView, or a\n * browser in dev mode) and read its console/network/DOM/screenshot over CDP plus\n * the AIT.* domain, without a human watching a phone. Transport is CDP-via-Chii:\n * a local Chii relay :9100 exposed through a cloudflared quick tunnel; the phone\n * attaches over the public wss URL.\n *\n * AI host --stdio--> this server --CDP client WS--> Chii relay :9100\n * ^-- target WS -- phone\n *\n * The tool layer reads from an injectable `CdpConnection` (CDP) and `AitSource`\n * (AIT.*), so every tool is unit-testable with a fake (no phone). This module\n * wires the live pieces (relay + tunnel + production connection); the phone\n * roundtrip is fully wired and pending only on-device acceptance.\n *\n * Node-only.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { ChiiAitSource } from './ait-chii-source.js';\nimport type { AitSource } from './ait-source.js';\nimport type { CdpConnection } from './cdp-connection.js';\nimport { ChiiCdpConnection } from './chii-connection.js';\nimport { startChiiRelay } from './chii-relay.js';\nimport {\n buildAttachUrl,\n DEBUG_TOOL_DEFINITIONS,\n getDomDocument,\n getMockState,\n getOperationalEnvironment,\n getSdkCallHistory,\n isAitToolName,\n isDebugToolName,\n listConsoleMessages,\n listNetworkRequests,\n listPages,\n type TunnelStatus,\n takeScreenshot,\n takeSnapshot,\n} from './tools.js';\nimport { verifyTotp } from './totp.js';\nimport {\n generateAttachToken,\n printAttachBanner,\n type QuickTunnel,\n startQuickTunnel,\n} from './tunnel.js';\n\n/** Live infra the connection reads tunnel status from. */\nexport interface DebugServerDeps {\n connection: CdpConnection;\n /** AIT.* domain source — forwarded over the same Chii channel in production. */\n aitSource: AitSource;\n /** Returns current tunnel status (URL changes per spawn). */\n getTunnelStatus(): TunnelStatus;\n}\n\n/**\n * Builds the debug-mode MCP server around an injected CDP connection + AIT\n * source + tunnel status getter. Pure wiring — does not start a relay or\n * tunnel, which is what makes the tool surface unit-testable.\n */\nexport function createDebugServer(deps: DebugServerDeps): Server {\n const { connection, aitSource, getTunnelStatus } = deps;\n\n const server = new Server(\n { name: 'ait-debug', version: __VERSION__ },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: DEBUG_TOOL_DEFINITIONS.map((tool) => ({ ...tool })),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n if (!isDebugToolName(name)) {\n return {\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n\n // AIT.* tools are served by the AIT source. In production it rides the same\n // Chii websocket as CDP, so the connection must be attached first; the AIT\n // source's sendCommand rejects with a clear message if no page is attached.\n if (isAitToolName(name)) {\n try {\n await connection.enableDomains();\n switch (name) {\n case 'AIT.getSdkCallHistory':\n return jsonResult(await getSdkCallHistory(aitSource));\n case 'AIT.getMockState':\n return jsonResult(await getMockState(aitSource));\n case 'AIT.getOperationalEnvironment':\n return jsonResult(await getOperationalEnvironment(aitSource));\n default:\n return unknownTool(name);\n }\n } catch (err) {\n return errorResult(err, name);\n }\n }\n\n // build_attach_url is pure synthesis (scheme URL + relay URL → deep link).\n // It works before any page attaches, so it must not require enableDomains.\n if (name === 'build_attach_url') {\n const schemeUrl = request.params.arguments?.scheme_url;\n if (typeof schemeUrl !== 'string' || schemeUrl === '') {\n return {\n content: [{ type: 'text', text: 'build_attach_url requires a non-empty scheme_url.' }],\n isError: true,\n };\n }\n try {\n return jsonResult(buildAttachUrl(schemeUrl, getTunnelStatus()));\n } catch (err) {\n return errorResult(err, name);\n }\n }\n\n try {\n // Ensure CDP domains are enabled before reading. No-op once attached;\n // throws a clear message while no page is attached yet.\n await connection.enableDomains();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (name === 'list_pages') {\n // list_pages is still useful pre-attach: report tunnel + empty pages.\n return jsonResult(listPages(connection, getTunnelStatus()));\n }\n return {\n content: [\n {\n type: 'text',\n text: `${message}\\nCall list_pages to confirm a mini-app has attached over the relay.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n switch (name) {\n case 'list_console_messages':\n return jsonResult(listConsoleMessages(connection));\n case 'list_network_requests':\n return jsonResult(listNetworkRequests(connection));\n case 'list_pages':\n return jsonResult(listPages(connection, getTunnelStatus()));\n case 'get_dom_document':\n return jsonResult(await getDomDocument(connection));\n case 'take_snapshot':\n return jsonResult(await takeSnapshot(connection));\n case 'take_screenshot': {\n const shot = await takeScreenshot(connection);\n return {\n content: [{ type: 'image' as const, data: shot.data, mimeType: shot.mimeType }],\n };\n }\n default:\n return unknownTool(name);\n }\n } catch (err) {\n return errorResult(err, name);\n }\n });\n\n return server;\n}\n\nfunction jsonResult(value: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }] };\n}\n\nfunction unknownTool(name: string) {\n return { content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }], isError: true };\n}\n\nfunction errorResult(err: unknown, name: string) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [\n {\n type: 'text' as const,\n text: `${name} failed: ${message}\\nCall list_pages to confirm a mini-app has attached over the relay.`,\n },\n ],\n isError: true,\n };\n}\n\nexport interface RunDebugServerOptions {\n /** Local Chii relay port. Default 9100. */\n relayPort?: number;\n}\n\n/**\n * Reads `AIT_DEBUG_TOTP_SECRET` from `process.env` at runtime and builds a\n * `verifyAuth` predicate for the Chii relay's WebSocket upgrade gate.\n *\n * The predicate checks the `at` query parameter against the current and\n * adjacent TOTP time steps (±1 skew) using `verifyTotp`.\n *\n * Returns `undefined` when the env var is not set — callers treat that as\n * \"auth disabled\" (no predicate registered on the relay).\n *\n * SECRET-HANDLING: The secret value read from env is captured in a closure and\n * is NEVER written to any log, error message, or process output.\n */\nexport function buildRelayVerifyAuth():\n | ((req: import('node:http').IncomingMessage) => boolean)\n | undefined {\n const secret = process.env.AIT_DEBUG_TOTP_SECRET;\n if (!secret) return undefined;\n\n return (req) => {\n // Parse the `at` query param from the upgrade request URL.\n // req.url is the raw request path + query, e.g. `/client/id?target=…&at=123456`\n const rawUrl = req.url ?? '';\n const qIndex = rawUrl.indexOf('?');\n const queryStr = qIndex === -1 ? '' : rawUrl.slice(qIndex + 1);\n const params = new URLSearchParams(queryStr);\n const code = params.get('at') ?? '';\n\n // Do NOT log `code`, `secret`, or any derived value here.\n return verifyTotp(secret, code);\n };\n}\n\n/**\n * Boots the live debug stack and serves it over stdio:\n * 1. start the Chii relay (with TOTP auth if AIT_DEBUG_TOTP_SECRET is set),\n * 2. open a cloudflared quick tunnel to it,\n * 3. print relay URL + attach instructions,\n * 4. expose the debug tools backed by a `ChiiCdpConnection` + `ChiiAitSource`.\n */\nexport async function runDebugServer(options: RunDebugServerOptions = {}): Promise<void> {\n const relayPort = options.relayPort ?? 9100;\n\n // Build the TOTP verifyAuth predicate from env at startup (runtime read).\n const verifyAuth = buildRelayVerifyAuth();\n const totpEnabled = verifyAuth !== undefined;\n\n const relay = await startChiiRelay({ port: relayPort, verifyAuth });\n\n let tunnel: QuickTunnel | null = null;\n let tunnelStatus: TunnelStatus = { up: false, wssUrl: null };\n // generateAttachToken is kept for legacy/non-TOTP token use, but we no\n // longer print it in the banner to avoid accidental secret exposure.\n const _token = generateAttachToken();\n\n try {\n tunnel = await startQuickTunnel(relayPort);\n tunnelStatus = { up: true, wssUrl: tunnel.wssUrl };\n await printAttachBanner({ wssUrl: tunnel.wssUrl, totpEnabled });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[ait-debug] Failed to open cloudflared quick tunnel: ${message}\\n` +\n '[ait-debug] The relay is up locally; attach over the public URL is unavailable until the tunnel starts.\\n',\n );\n }\n\n const connection = new ChiiCdpConnection({ relayBaseUrl: relay.baseUrl });\n // AIT.* methods ride the same Chii channel as CDP commands.\n const aitSource = new ChiiAitSource(connection);\n const server = createDebugServer({\n connection,\n aitSource,\n getTunnelStatus: () => tunnelStatus,\n });\n\n const transport = new StdioServerTransport();\n\n const shutdown = () => {\n connection.close();\n tunnel?.stop();\n void relay.close();\n void server.close();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n\n await server.connect(transport);\n}\n","/**\n * Dev-mode `AitSource` — backed by the Vite dev server's mock-state endpoint.\n *\n * The dev server already exposes the live browser mock state at\n * `GET /api/ait-devtools/state` (registered by the unplugin with `mcp: true`).\n * Phase 3 aligns dev mode and debug mode on the same `AIT.*` tool surface, so\n * dev mode serves those tools off this one HTTP source instead of a CDP channel:\n *\n * - `AIT.getMockState` → the full state snapshot (verbatim).\n * - `AIT.getOperationalEnvironment` → derived from the snapshot's\n * `environment` + `appVersion` fields.\n * - `AIT.getSdkCallHistory` → empty (the dev endpoint does not record\n * an SDK call trace — honest, not faked).\n *\n * An AI agent thus sees the same `AIT.getMockState` tool whether attached to a\n * phone (debug) or a dev browser (dev). Tests inject a fake `fetch`.\n */\n\nimport type {\n AitMethodMap,\n AitMethodName,\n AitMockState,\n AitOperationalEnvironment,\n AitSdkCallHistory,\n AitSource,\n} from './ait-source.js';\n\n/** Minimal `fetch` shape this source needs (injectable in tests). */\nexport type FetchLike = (url: string) => Promise<{\n ok: boolean;\n status: number;\n statusText: string;\n json(): Promise<unknown>;\n}>;\n\nexport interface HttpAitSourceOptions {\n /** Full URL of the mock-state endpoint, e.g. `http://localhost:5173/api/ait-devtools/state`. */\n stateEndpoint: string;\n /** Injected for tests; defaults to global `fetch`. */\n fetchImpl?: FetchLike;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport class HttpAitSource implements AitSource {\n private readonly stateEndpoint: string;\n private readonly fetchImpl: FetchLike;\n\n constructor(options: HttpAitSourceOptions) {\n this.stateEndpoint = options.stateEndpoint;\n this.fetchImpl = options.fetchImpl ?? ((url) => fetch(url));\n }\n\n private async fetchState(): Promise<AitMockState> {\n const res = await this.fetchImpl(this.stateEndpoint);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch mock state from ${this.stateEndpoint}: HTTP ${res.status} ${res.statusText}. ` +\n 'Ensure the Vite dev server is running with the @ait-co/devtools unplugin option `mcp: true`.',\n );\n }\n const body = await res.json();\n return isObject(body) ? body : {};\n }\n\n async get<M extends AitMethodName>(method: M): Promise<AitMethodMap[M]> {\n switch (method) {\n case 'AIT.getMockState': {\n const state = await this.fetchState();\n return state as AitMethodMap[M];\n }\n case 'AIT.getOperationalEnvironment': {\n const state = await this.fetchState();\n const environment = typeof state.environment === 'string' ? state.environment : 'unknown';\n const sdkVersion = typeof state.appVersion === 'string' ? state.appVersion : null;\n const result: AitOperationalEnvironment = { environment, sdkVersion };\n return result as AitMethodMap[M];\n }\n case 'AIT.getSdkCallHistory': {\n // sdkCallLog slice is now part of the mock state pushed by the browser panel.\n // Read it from the state snapshot rather than returning an empty stub.\n const state = await this.fetchState();\n const raw = state.sdkCallLog;\n const calls = Array.isArray(raw) ? (raw as AitSdkCallHistory['calls']) : [];\n const result: AitSdkCallHistory = { calls };\n return result as AitMethodMap[M];\n }\n default:\n throw new Error(`Unknown AIT method: ${String(method)}`);\n }\n }\n}\n","/**\n * @ait-co/devtools dev-mode MCP server (stdio).\n *\n * Exposes the live browser mock state from a running Vite dev server to AI\n * coding agents via the Model Context Protocol (MCP).\n *\n * Architecture:\n * Browser (aitState) → Vite dev server endpoint (/api/ait-devtools/state)\n * ← HTTP GET ← this stdio MCP server ← AI agent\n *\n * The Vite endpoint is registered by the unplugin when `mcp: true` is set in\n * the plugin options (see `src/unplugin/index.ts`).\n *\n * Phase 3 tool-surface alignment: dev mode and debug mode now expose the same\n * `AIT.*` tools (`AIT.getMockState`, `AIT.getOperationalEnvironment`,\n * `AIT.getSdkCallHistory`). In dev mode they are backed by the HTTP mock-state\n * endpoint (see `HttpAitSource`); in debug mode by the Chii channel. So an AI\n * sees a coherent tool whether attached to a phone (debug) or a dev browser\n * (dev). `devtools_get_mock_state` (the original devtools#130 name) is kept as a\n * backward-compatible alias of `AIT.getMockState`.\n *\n * This module is reached via the `devtools-mcp --mode=dev` CLI entry (see\n * `cli.ts`); the default (no flag) bin mode is the debug-mode CDP/Chii server.\n *\n * Usage (in your MCP client config, e.g. Claude Desktop):\n * {\n * \"mcpServers\": {\n * \"ait-devtools\": {\n * \"command\": \"pnpm\",\n * \"args\": [\"exec\", \"devtools-mcp\", \"--mode=dev\"],\n * \"env\": { \"AIT_DEVTOOLS_URL\": \"http://localhost:5173\" }\n * }\n * }\n * }\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { HttpAitSource } from './ait-http-source.js';\nimport type { AitSource } from './ait-source.js';\nimport {\n getMockState,\n getOperationalEnvironment,\n getSdkCallHistory,\n isAitToolName,\n} from './tools.js';\n\n/** Tool descriptors served by the dev-mode server. */\nconst DEV_TOOL_DEFINITIONS = [\n {\n name: 'AIT.getMockState',\n description:\n 'Returns the devtools mock state snapshot (window.__ait) from the running browser session — ' +\n 'environment, permissions, location, auth, network, IAP, and more. Read-only. ' +\n 'Requires the Vite dev server running with the @ait-co/devtools unplugin option `mcp: true`. ' +\n 'Same tool as in debug mode, where the in-app side reports it over the AIT domain.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getOperationalEnvironment',\n description:\n 'Returns the operational environment + SDK/app version derived from the dev mock state. ' +\n 'Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'AIT.getSdkCallHistory',\n description:\n 'Returns the SDK call trace. In dev mode the HTTP mock-state endpoint records no trace, so ' +\n 'this returns an empty list; in debug mode it is populated over the AIT domain. Read-only.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n {\n name: 'devtools_get_mock_state',\n description:\n 'Backward-compatible alias of AIT.getMockState (the original devtools#130 name). Returns the ' +\n 'current AIT DevTools mock state snapshot. Read-only. Prefer AIT.getMockState in new configs.',\n inputSchema: { type: 'object', properties: {}, required: [] },\n },\n] as const;\n\nconst DEV_TOOL_NAMES = new Set<string>(DEV_TOOL_DEFINITIONS.map((t) => t.name));\n\nexport interface CreateDevServerDeps {\n /** AIT source for the dev tools. Defaults to an HTTP source over the dev server. */\n aitSource?: AitSource;\n}\n\n/** Builds the dev-mode MCP server (does not connect a transport). */\nexport function createDevServer(deps: CreateDevServerDeps = {}): Server {\n const devtoolsUrl = process.env.AIT_DEVTOOLS_URL ?? 'http://localhost:5173';\n const stateEndpoint = `${devtoolsUrl}/api/ait-devtools/state`;\n const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });\n\n const server = new Server(\n { name: 'ait-devtools', version: __VERSION__ },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })),\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n if (!DEV_TOOL_NAMES.has(name)) {\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n\n try {\n // `devtools_get_mock_state` is an alias of `AIT.getMockState`.\n const effective = name === 'devtools_get_mock_state' ? 'AIT.getMockState' : name;\n if (!isAitToolName(effective)) {\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n switch (effective) {\n case 'AIT.getMockState':\n return jsonResult(await getMockState(aitSource));\n case 'AIT.getOperationalEnvironment':\n return jsonResult(await getOperationalEnvironment(aitSource));\n case 'AIT.getSdkCallHistory':\n return jsonResult(await getSdkCallHistory(aitSource));\n default:\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [\n {\n type: 'text',\n text:\n `${message}\\n` +\n 'Is the Vite dev server running with the @ait-co/devtools unplugin option `mcp: true`? ' +\n 'Is AIT_DEVTOOLS_URL set correctly?',\n },\n ],\n isError: true,\n };\n }\n });\n\n return server;\n}\n\nfunction jsonResult(value: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }] };\n}\n\n/** Builds the dev-mode server and connects it over stdio. */\nexport async function runDevServer(): Promise<void> {\n const server = createDevServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","/**\n * `devtools-mcp` bin entry.\n *\n * Single bin, two transports selected by `--mode`:\n * - (default, no flag) debug mode — CDP/Chii relay + cloudflared quick tunnel.\n * Attach a running mini-app (real Toss WebView or a browser) and read its\n * console + network over CDP without a human watching a phone.\n * - `--mode=dev` — dev mode — reads the live browser mock state from a running\n * Vite dev server (the devtools#130 `devtools_get_mock_state` surface).\n *\n * Node-only stdio process.\n */\n\nimport { argv } from 'node:process';\nimport { fileURLToPath } from 'node:url';\nimport { runDebugServer } from './debug-server.js';\nimport { runDevServer } from './server.js';\n\ntype Mode = 'debug' | 'dev';\n\n/** Parses `--mode=<value>` / `--mode <value>` from argv; default `debug`. */\nexport function parseMode(argv: readonly string[]): Mode {\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === undefined) continue;\n if (arg.startsWith('--mode=')) {\n return normalizeMode(arg.slice('--mode='.length));\n }\n if (arg === '--mode') {\n const next = argv[i + 1];\n if (next === undefined) {\n throw new Error(\"--mode requires a value: 'debug' (default) or 'dev'.\");\n }\n return normalizeMode(next);\n }\n }\n return 'debug';\n}\n\nfunction normalizeMode(value: string): Mode {\n if (value === 'dev') return 'dev';\n if (value === 'debug') return 'debug';\n throw new Error(`Unknown --mode '${value}'. Expected 'debug' (default) or 'dev'.`);\n}\n\nasync function main(): Promise<void> {\n const mode = parseMode(process.argv.slice(2));\n if (mode === 'dev') {\n await runDevServer();\n } else {\n await runDebugServer();\n }\n}\n\n/** True when this file is the process entry (the bin), not an import. */\nfunction isEntrypoint(): boolean {\n const entry = argv[1];\n if (entry === undefined) return false;\n try {\n return fileURLToPath(import.meta.url) === entry;\n } catch {\n return false;\n }\n}\n\nif (isEntrypoint()) {\n main().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[devtools-mcp] fatal: ${message}\\n`);\n process.exitCode = 1;\n });\n}\n"],"mappings":"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"}
@@ -17,6 +17,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
17
  * and unit-testable with a fake that returns canned AIT responses. No phone and
18
18
  * no running dev server are needed in tests.
19
19
  */
20
+ /**
21
+ * Mock fidelity grade of the SDK call.
22
+ * - `faithful` — mock faithfully reproduces the real SDK contract (🟢).
23
+ * - `partial` — mock partially matches; edge cases may differ from real (🟡).
24
+ * - `inert` — mock accepts the call but produces no observable effect (🔴).
25
+ */
26
+ type AitSdkCallFidelity = 'faithful' | 'partial' | 'inert';
20
27
  /** One entry of the SDK-call trace returned by `AIT.getSdkCallHistory`. */
21
28
  interface AitSdkCall {
22
29
  /** SDK method name, e.g. `getOperationalEnvironment`, `saveBase64Data`. */
@@ -31,6 +38,8 @@ interface AitSdkCall {
31
38
  result?: unknown;
32
39
  /** Error message when `status === 'rejected'`. */
33
40
  error?: string;
41
+ /** Mock fidelity grade — how closely this mock reproduces the real SDK behaviour. */
42
+ fidelity: AitSdkCallFidelity;
34
43
  }
35
44
  /** Result of `AIT.getSdkCallHistory`. */
36
45
  interface AitSdkCallHistory {
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/mcp/ait-source.ts","../../src/mcp/server.ts"],"mappings":";;;;;;;AAiBA;;;;;;;;;;;;AAgBA;AAAA,UAhBiB,UAAA;;EAEf,MAAA;EAeiB;EAbjB,IAAA;EAsBsB;EApBtB,SAAA;EAoByB;EAlBzB,MAAA;EAqBe;EAnBf,MAAA;;EAEA,KAAA;AAAA;AAyBF;AAAA,UArBiB,iBAAA;EACf,KAAA,EAAO,UAAA;AAAA;;;;;;;KASG,YAAA,GAAe,MAAA;;UAGV,yBAAA;EAWkB;EATjC,WAAA;EAS0D;EAP1D,UAAA;AAAA;;UAIe,YAAA;EACf,uBAAA,EAAyB,iBAAA;EACzB,kBAAA,EAAoB,YAAA;EACpB,+BAAA,EAAiC,yBAAA;AAAA;AAAA,KAGvB,aAAA,SAAsB,YAAA;;;;;UAMjB,SAAA;EACf,GAAA,WAAc,aAAA,EAAe,MAAA,EAAQ,CAAA,GAAI,OAAA,CAAQ,YAAA,CAAa,CAAA;AAAA;;;UCiB/C,mBAAA;ED7Bf;EC+BA,SAAA,GAAY,SAAA;AAAA;;iBAIE,eAAA,CAAgB,IAAA,GAAM,mBAAA,GAA2B,MAAA;;iBA6D3C,YAAA,CAAA,GAAgB,OAAA"}
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/mcp/ait-source.ts","../../src/mcp/server.ts"],"mappings":";;;;;;;AAsBA;;;;;AAGA;;;;;;;;;;;;;KAHY,kBAAA;AAqBZ;AAAA,UAlBiB,UAAA;;EAEf,MAAA;EAiBiB;EAfjB,IAAA;EAwBsB;EAtBtB,SAAA;EAsByB;EApBzB,MAAA;EAuBe;EArBf,MAAA;;EAEA,KAAA;EAuBU;EArBV,QAAA,EAAU,kBAAA;AAAA;;UAIK,iBAAA;EACf,KAAA,EAAO,UAAA;AAAA;;;;;;;KASG,YAAA,GAAe,MAAA;;UAGV,yBAAA;EAW2C;EAT1D,WAAA;EAYuB;EAVvB,UAAA;AAAA;;UAIe,YAAA;EACf,uBAAA,EAAyB,iBAAA;EACzB,kBAAA,EAAoB,YAAA;EACpB,+BAAA,EAAiC,yBAAA;AAAA;AAAA,KAGvB,aAAA,SAAsB,YAAA;;;;;UAMjB,SAAA;EACf,GAAA,WAAc,aAAA,EAAe,MAAA,EAAQ,CAAA,GAAI,OAAA,CAAQ,YAAA,CAAa,CAAA;AAAA;;;UCO/C,mBAAA;EDpBY;ECsB3B,SAAA,GAAY,SAAA;AAAA;;iBAIE,eAAA,CAAgB,IAAA,GAAM,mBAAA,GAA2B,MAAA;;iBA6D3C,YAAA,CAAA,GAAgB,OAAA"}
@@ -29,7 +29,10 @@ var HttpAitSource = class {
29
29
  sdkVersion: typeof state.appVersion === "string" ? state.appVersion : null
30
30
  };
31
31
  }
32
- case "AIT.getSdkCallHistory": return { calls: [] };
32
+ case "AIT.getSdkCallHistory": {
33
+ const raw = (await this.fetchState()).sdkCallLog;
34
+ return { calls: Array.isArray(raw) ? raw : [] };
35
+ }
33
36
  default: throw new Error(`Unknown AIT method: ${String(method)}`);
34
37
  }
35
38
  }
@@ -101,6 +104,15 @@ new Set([
101
104
  required: []
102
105
  }
103
106
  },
107
+ {
108
+ name: "measure_safe_area",
109
+ description: "Runs a safe-area probe on the attached mini-app page via Runtime.evaluate and returns normalized safe-area insets, viewport geometry, device pixel ratio, and User-Agent. Read-only — does not modify page state. Use in a relay session (phone attached) to get ground-truth values for upgrading a viewport preset from extrapolated/placeholder to measured. Requires the relay to be attached — call list_pages first.",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {},
113
+ required: []
114
+ }
115
+ },
104
116
  {
105
117
  name: "AIT.getSdkCallHistory",
106
118
  description: "Returns the recent Apps In Toss SDK call trace (method, args, result/error, timestamp) that raw CDP cannot observe. Read-only. Use to confirm an SDK call fired and how it resolved (e.g. a saveBase64Data permission regression).",
@@ -129,6 +141,45 @@ new Set([
129
141
  }
130
142
  }
131
143
  ].map((t) => t.name));
144
+ `
145
+ (function() {
146
+ var el = document.createElement('div');
147
+ el.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;visibility:hidden;' +
148
+ 'padding-top:env(safe-area-inset-top,0px);' +
149
+ 'padding-right:env(safe-area-inset-right,0px);' +
150
+ 'padding-bottom:env(safe-area-inset-bottom,0px);' +
151
+ 'padding-left:env(safe-area-inset-left,0px)';
152
+ document.documentElement.appendChild(el);
153
+ var cs = window.getComputedStyle(el);
154
+ var cssEnv = {
155
+ top: parseFloat(cs.paddingTop) || 0,
156
+ right: parseFloat(cs.paddingRight) || 0,
157
+ bottom: parseFloat(cs.paddingBottom) || 0,
158
+ left: parseFloat(cs.paddingLeft) || 0
159
+ };
160
+ document.documentElement.removeChild(el);
161
+ var sdkInsets = null;
162
+ try {
163
+ if (typeof SafeAreaInsets !== 'undefined' && SafeAreaInsets && typeof SafeAreaInsets.get === 'function') {
164
+ sdkInsets = SafeAreaInsets.get();
165
+ }
166
+ } catch(_) {}
167
+ var navBarHeight = null;
168
+ try {
169
+ var nb = document.querySelector('.ait-navbar');
170
+ if (nb) navBarHeight = nb.getBoundingClientRect().height;
171
+ } catch(_) {}
172
+ return JSON.stringify({
173
+ cssEnv: cssEnv,
174
+ sdkInsets: sdkInsets,
175
+ navBarHeight: navBarHeight,
176
+ innerWidth: window.innerWidth,
177
+ innerHeight: window.innerHeight,
178
+ devicePixelRatio: window.devicePixelRatio,
179
+ userAgent: navigator.userAgent
180
+ });
181
+ })()
182
+ `.trim();
132
183
  /** Set of tool names served by the AIT source rather than the CDP connection. */
133
184
  const AIT_TOOL_NAMES = new Set([
134
185
  "AIT.getSdkCallHistory",
@@ -234,7 +285,7 @@ function createDevServer(deps = {}) {
234
285
  const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
235
286
  const server = new Server({
236
287
  name: "ait-devtools",
237
- version: "0.1.32"
288
+ version: "0.1.33"
238
289
  }, { capabilities: { tools: {} } });
239
290
  server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
240
291
  server.setRequestHandler(CallToolRequestSchema, async (request) => {