@ait-co/devtools 0.1.70 → 0.1.71
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.
- package/dist/{chii-relay-D5Hc0G39.cjs → chii-relay-BcnVJBqm.cjs} +76 -3
- package/dist/chii-relay-BcnVJBqm.cjs.map +1 -0
- package/dist/{chii-relay-BcWDKbQ1.js → chii-relay-DSVG4Ui1.js} +76 -3
- package/dist/chii-relay-DSVG4Ui1.js.map +1 -0
- package/dist/devtools-opener-BbUXBzgA.js.map +1 -1
- package/dist/devtools-opener-Bp671YXu.cjs.map +1 -1
- package/dist/devtools-opener-D84kZFtR.js.map +1 -1
- package/dist/devtools-opener-h6A-UjzC.cjs.map +1 -1
- package/dist/mcp/cli.js +171 -43
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mock/index.d.ts +24 -1
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +89 -1
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +2 -2
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.js +1 -1
- package/package.json +1 -1
- package/dist/chii-relay-BcWDKbQ1.js.map +0 -1
- package/dist/chii-relay-D5Hc0G39.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools-opener-h6A-UjzC.cjs","names":[],"sources":["../src/mcp/devtools-opener.ts"],"sourcesContent":["/**\n * Auto-opens Chrome DevTools when a page attaches over the Chii relay.\n *\n * When a real device attaches (env 2 / 3 / 4 in the 4-environments fidelity\n * ladder), the Chii relay exposes a standard CDP WebSocket endpoint. The\n * Chrome DevTools frontend can connect to any such endpoint via:\n *\n * https://chrome-devtools-frontend.appspot.com/serve_file/@/inspector.html\n * ?wss=<host>[/<path>]\n * &panel=console\n *\n * Where `<host>` is the public WSS relay URL without the `wss://` scheme prefix\n * (the DevTools frontend adds it). This module assembles that URL and opens it\n * in the OS default browser so the developer immediately gets a full Chrome\n * DevTools UI.\n *\n * IMPORTANT — environment guard:\n * Auto-open only fires in relay environments (env 2 / 3 / 4). In env 1\n * (local browser + mock SDK) the developer already has F12 available; opening\n * a DevTools window pointing at the mock relay would be confusing and useless.\n * The caller (`startAttachWatcher` in `debug-server.ts`) passes the current\n * environment and this module bails out when it is `mock`.\n *\n * Opt-out: set `AIT_AUTO_DEVTOOLS=0` in the environment to suppress auto-open\n * entirely. Any other value (or absent) enables the default behaviour.\n *\n * Duplicate-open guard:\n * `AutoDevtoolsOpener` tracks whether open was already triggered for the\n * current session. The open fires at most once per instance — typically one\n * per `runDebugServer` call.\n *\n * PWA (WebKit) caveat:\n * The Chii relay injects a chobitsu CDP shim into WebKit-based runtimes (env 2\n * AITC Sandbox PWA). The DevTools frontend will connect and most panels work.\n * However, WebKit does not expose the full CDP domain set that V8/Blink does,\n * so some panels (Network, Layers) may appear empty or show limited data.\n * This is a WebKit runtime constraint, not a relay or devtools-opener issue.\n *\n * Node-only: uses `child_process.spawnSync` to invoke the OS open command.\n */\n\nimport type { McpEnvironment } from './environment.js';\n\n// ---------------------------------------------------------------------------\n// Chrome DevTools frontend URL\n// ---------------------------------------------------------------------------\n\n/**\n * Base URL for the Chrome DevTools inspector hosted on appspot.\n *\n * The `@` path segment is the \"latest / bleeding edge\" alias which tracks the\n * current Chrome stable CDP protocol version — compatible with the chobitsu-\n * based CDP that Chii injects. A specific commit hash may be pinned here if\n * a regression is observed.\n */\nconst DEVTOOLS_FRONTEND_BASE =\n 'https://chrome-devtools-frontend.appspot.com/serve_file/@/inspector.html';\n\n// ---------------------------------------------------------------------------\n// URL assembly\n// ---------------------------------------------------------------------------\n\n/**\n * Assembles the Chrome DevTools inspector URL that connects to a Chii relay\n * WebSocket.\n *\n * The `wss=` parameter expects a host-and-path string without the `wss://`\n * scheme prefix — the DevTools frontend prepends it automatically.\n *\n * @param wssRelayUrl - Full `wss://` URL of the Chii relay (public tunnel).\n * Example: `wss://abc.trycloudflare.com`\n * @param panel - Initial panel. Defaults to `\"console\"`.\n *\n * @example\n * buildChromeDevtoolsUrl('wss://abc.trycloudflare.com')\n * // → 'https://chrome-devtools-frontend.appspot.com/serve_file/@/inspector.html?wss=abc.trycloudflare.com&panel=console'\n */\nexport function buildChromeDevtoolsUrl(\n wssRelayUrl: string,\n panel: 'elements' | 'console' | 'sources' | 'network' = 'console',\n): string {\n // Strip `wss://` prefix — the DevTools frontend expects host[/path] only.\n const wssParam = wssRelayUrl.replace(/^wss:\\/\\//i, '');\n const params = new URLSearchParams({ wss: wssParam, panel });\n return `${DEVTOOLS_FRONTEND_BASE}?${params.toString()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Opt-out check\n// ---------------------------------------------------------------------------\n\n/**\n * Returns `true` when auto-open is **disabled** via the `AIT_AUTO_DEVTOOLS`\n * env var. Only the explicit `\"0\"` value disables it; anything else (including\n * absent) leaves auto-open enabled.\n */\nexport function isAutoDevtoolsDisabled(): boolean {\n return process.env.AIT_AUTO_DEVTOOLS === '0';\n}\n\n// ---------------------------------------------------------------------------\n// Browser open (Node-only, sync)\n// ---------------------------------------------------------------------------\n\n/**\n * Opens the given URL in the OS default browser using a platform-appropriate\n * command. Returns `true` on success.\n *\n * Failures are silent from the caller's perspective — the caller should log\n * the URL to stderr as a fallback before calling this function.\n */\nexport function openUrlInBrowser(url: string): boolean {\n // Test hook: skip actual spawn when running in vitest / CI where the OS open\n // command may hang or be absent. Production code never sets this.\n if (process.env.AIT_AUTO_DEVTOOLS_TEST_SKIP_SPAWN === '1') return false;\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { spawnSync } = require('node:child_process') as typeof import('node:child_process');\n const platform = process.platform;\n\n type Candidate = { cmd: string; args: string[] };\n let candidates: Candidate[];\n if (platform === 'darwin') {\n candidates = [{ cmd: 'open', args: [url] }];\n } else if (platform === 'win32') {\n candidates = [{ cmd: 'cmd', args: ['/c', 'start', '', url] }];\n } else {\n // Linux + fallback\n candidates = [\n { cmd: 'xdg-open', args: [url] },\n { cmd: 'sensible-browser', args: [url] },\n { cmd: 'x-www-browser', args: [url] },\n ];\n }\n\n for (const { cmd, args } of candidates) {\n try {\n const result = spawnSync(cmd, args, { encoding: 'utf8', timeout: 5_000 });\n if (!result.error && result.status === 0) return true;\n } catch {\n // Try next candidate.\n }\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// AutoDevtoolsOpener — stateful once-per-session open guard\n// ---------------------------------------------------------------------------\n\n/**\n * Manages auto-opening Chrome DevTools exactly once per relay attach session.\n *\n * Create one instance per `runDebugServer` call and pass its `open()` method\n * as the `onFirstAttach` callback to `startAttachWatcher`.\n *\n * The open fires at most once. Subsequent `open()` calls are no-ops.\n * Opt-out and mock-environment guard are checked at call time.\n */\nexport class AutoDevtoolsOpener {\n private _opened = false;\n\n /**\n * Attempts to auto-open Chrome DevTools.\n *\n * No-op when any of the following conditions hold:\n * 1. Already opened this session (`_opened` is true).\n * 2. `AIT_AUTO_DEVTOOLS=0` opt-out is set.\n * 3. Environment is `mock` (env 1 — F12 is already available).\n * 4. `wssRelayUrl` is null/undefined/empty (tunnel not yet up).\n *\n * Always writes the DevTools URL to stderr so the developer can copy it\n * if the browser open fails or the popup is blocked.\n *\n * @param wssRelayUrl - The public `wss://` relay URL (from tunnel status).\n * @param env - Current MCP environment (`mock` | `relay`).\n */\n open(wssRelayUrl: string | null | undefined, env: McpEnvironment): void {\n if (this._opened) return;\n if (isAutoDevtoolsDisabled()) return;\n if (env === 'mock') return;\n if (!wssRelayUrl) return;\n\n this._opened = true;\n\n const devtoolsUrl = buildChromeDevtoolsUrl(wssRelayUrl);\n\n process.stderr.write(\n '[ait-debug] 기기가 연결됐습니다 — Chrome DevTools를 자동으로 엽니다.\\n' +\n `[ait-debug] Chrome DevTools URL: ${devtoolsUrl}\\n` +\n '[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)\\n',\n );\n\n const opened = openUrlInBrowser(devtoolsUrl);\n if (!opened) {\n process.stderr.write(\n '[ait-debug] 브라우저 자동 열기 실패 — 위 URL을 브라우저에서 직접 여세요.\\n',\n );\n }\n }\n\n /** Returns `true` if `open()` has passed all guards and fired once. */\n get opened(): boolean {\n return this._opened;\n }\n}\n"],"mappings":";;;;;;AAgGA,SAAgB,yBAAkC;AAChD,QAAO,QAAQ,IAAI,sBAAsB;;;;;;;;;AAc3C,SAAgB,iBAAiB,KAAsB;AAGrD,KAAI,QAAQ,IAAI,sCAAsC,IAAK,QAAO;CAElE,MAAM,EAAE,cAAc,QAAQ,qBAAqB;CACnD,MAAM,WAAW,QAAQ;CAGzB,IAAI;AACJ,KAAI,aAAa,SACf,cAAa,CAAC;EAAE,KAAK;EAAQ,MAAM,CAAC,IAAI;EAAE,CAAC;UAClC,aAAa,QACtB,cAAa,CAAC;EAAE,KAAK;EAAO,MAAM;GAAC;GAAM;GAAS;GAAI;GAAI;EAAE,CAAC;KAG7D,cAAa;EACX;GAAE,KAAK;GAAY,MAAM,CAAC,IAAI;GAAE;EAChC;GAAE,KAAK;GAAoB,MAAM,CAAC,IAAI;GAAE;EACxC;GAAE,KAAK;GAAiB,MAAM,CAAC,IAAI;GAAE;EACtC;AAGH,MAAK,MAAM,EAAE,KAAK,UAAU,WAC1B,KAAI;EACF,MAAM,SAAS,UAAU,KAAK,MAAM;GAAE,UAAU;GAAQ,SAAS;GAAO,CAAC;AACzE,MAAI,CAAC,OAAO,SAAS,OAAO,WAAW,EAAG,QAAO;SAC3C;AAIV,QAAO"}
|
|
1
|
+
{"version":3,"file":"devtools-opener-h6A-UjzC.cjs","names":[],"sources":["../src/mcp/devtools-opener.ts"],"sourcesContent":["/**\n * Auto-opens Chrome DevTools when a page attaches over the Chii relay.\n *\n * When a real device attaches (env 2 / 3 / 4 in the 4-environments fidelity\n * ladder), the Chii relay exposes a standard CDP WebSocket endpoint. Chii\n * also self-hosts its DevTools frontend at:\n *\n * <relay-base>/front_end/chii_app.html\n * ?ws|wss=<encodeURIComponent(\"<relay-host>/client/<uuid>?target=<targetId>&at=<totp>\")>\n *\n * The param name follows the relay base scheme — `ws=` for plain HTTP\n * (env 3/4 local relay), `wss=` for HTTPS (env 2 tunnel) — matching the\n * scheme branch in chii/public/index.js.\n *\n * This is the same URL format that Chii's own index-page inspect-links use\n * (derived from `chii/public/index.js` — the JS that powers the target list\n * page at `<relay-base>/`). Opening this URL in the developer's local browser\n * gives a full Chrome DevTools UI connected to the phone via the relay.\n *\n * IMPORTANT — environment guard:\n * Auto-open only fires in relay environments (env 2 / 3 / 4). In env 1\n * (local browser + mock SDK) the developer already has F12 available; opening\n * a DevTools window pointing at the mock relay would be confusing and useless.\n * The caller (`startAttachWatcher` in `debug-server.ts`) passes the current\n * environment and this module bails out when it is `mock`.\n *\n * Opt-out: set `AIT_AUTO_DEVTOOLS=0` in the environment to suppress auto-open\n * entirely. Any other value (or absent) enables the default behaviour.\n *\n * Duplicate-open guard:\n * `AutoDevtoolsOpener` tracks whether open was already triggered for the\n * current session. The open fires at most once per instance — typically one\n * per `runDebugServer` call.\n *\n * TOTP expiry caveat:\n * The `at=` TOTP code embedded in the `wss=` parameter is minted fresh at the\n * moment `open()` is called. The code is valid for the 30-second RFC 6238\n * window (±1 step skew = 90 s acceptance). If the developer does not open the\n * URL within that window the WebSocket upgrade will be rejected with 4401.\n * In practice the browser opens immediately after the OS `open` command, so\n * the window is always satisfied; if it is not (e.g. the URL is copied and\n * opened later) the developer can copy the wss= param, replace `at=`, and\n * reload. This is documented in the JSDoc below.\n *\n * PWA (WebKit) caveat:\n * The Chii relay injects a chobitsu CDP shim into WebKit-based runtimes (env 2\n * AITC Sandbox PWA). The DevTools frontend will connect and most panels work.\n * However, WebKit does not expose the full CDP domain set that V8/Blink does,\n * so some panels (Network, Layers) may appear empty or show limited data.\n * This is a WebKit runtime constraint, not a relay or devtools-opener issue.\n *\n * Node-only: uses `child_process.spawnSync` to invoke the OS open command.\n */\n\nimport type { McpEnvironment } from './environment.js';\n\n// ---------------------------------------------------------------------------\n// Chii self-hosted DevTools frontend URL\n// ---------------------------------------------------------------------------\n\n/**\n * Assembles the Chii self-hosted DevTools inspector URL for a given relay\n * and target.\n *\n * Chii serves its own DevTools frontend at\n * `<relayHttpBaseUrl>/front_end/chii_app.html`. The `ws=` (plain HTTP relay)\n * or `wss=` (HTTPS relay) query parameter is a URL-encoded string of the form\n * `<relay-host>/client/<uuid>?target=<id>` (and optionally `&at=<totp>`) —\n * the same format used by Chii's own target list page (derived from\n * `chii/public/index.js`).\n *\n * The `at=` TOTP code is minted at call time via `mintTotp()`. It is valid\n * for the current 30-second RFC 6238 step (±1 step skew = 90 s acceptance\n * window). The developer must open the returned URL within that window. If\n * the window expires before the browser connects, the relay will reject the\n * WebSocket upgrade with close code 4401.\n *\n * SECRET-HANDLING: `mintTotp` returns a code, not a secret. The code is\n * embedded in the `wss=` parameter (inside the `at=` param) of the returned\n * URL. Callers MUST NOT log the returned URL to stdout (stderr is OK — it is\n * the intended fallback surface for the developer to copy the URL).\n *\n * @param relayHttpBaseUrl - Local HTTP base URL of the Chii relay, e.g.\n * `http://127.0.0.1:9100`. No trailing slash.\n * @param targetId - Chii target id (from `GET <relay>/targets`).\n * @param mintTotp - Optional function that returns a fresh 6-digit TOTP code\n * string. Called at most once. When omitted (TOTP disabled) no `at=` param\n * is added.\n * @param panel - Initial panel. Defaults to `\"console\"`.\n *\n * @example\n * buildChiiInspectorUrl(\n * 'http://127.0.0.1:9100',\n * 'abc123',\n * () => generateTotp(secret),\n * )\n * // → 'http://127.0.0.1:9100/front_end/chii_app.html?ws=127.0.0.1%3A9100%2Fclient%2F<uuid>%3Ftarget%3Dabc123%26at%3D<code>'\n */\nexport function buildChiiInspectorUrl(\n relayHttpBaseUrl: string,\n targetId: string,\n mintTotp?: () => string,\n panel: 'elements' | 'console' | 'sources' | 'network' = 'console',\n): string {\n // Extract the host (and port) from the relay HTTP base URL, and pick the\n // query param name chii_app.html expects: `ws=` dials `ws://` (plain-HTTP\n // relay — env 3/4 local 127.0.0.1) while `wss=` dials `wss://` (HTTPS\n // tunnel — env 2). chii/public/index.js does the same scheme branch:\n // `location.protocol === 'https:' ? 'wss' : 'ws'`. Always sending `wss=`\n // would make the frontend attempt TLS against the plain-HTTP local relay.\n let relayHost: string;\n let wsParamName: 'ws' | 'wss';\n try {\n const parsed = new URL(relayHttpBaseUrl);\n relayHost = parsed.host; // e.g. \"127.0.0.1:9100\"\n wsParamName = parsed.protocol === 'https:' ? 'wss' : 'ws';\n } catch {\n // Fallback: strip the scheme prefix manually if URL parsing fails.\n relayHost = relayHttpBaseUrl.replace(/^https?:\\/\\//i, '');\n wsParamName = /^https:/i.test(relayHttpBaseUrl) ? 'wss' : 'ws';\n }\n\n // Generate a client UUID that matches the format Chii's index.js uses\n // (6 random alphanumeric characters).\n const clientId = `devtools-opener-${Date.now().toString(36)}`;\n\n // Build the ws=/wss= value: \"<relay-host>/client/<uuid>?target=<id>[&at=<code>]\"\n // This mirrors the format from chii/public/index.js:\n // `${domain}${basePath}client/${randomId(6)}?target=${targetId}`\n let wsPath = `${relayHost}/client/${clientId}?target=${encodeURIComponent(targetId)}`;\n\n if (mintTotp) {\n // SECRET-HANDLING: mintTotp() returns a code (not a secret). The code\n // rides only in the URL's at= param. Callers must not log the URL.\n const code = mintTotp();\n wsPath += `&at=${encodeURIComponent(code)}`;\n }\n\n const params = new URLSearchParams({ [wsParamName]: wsPath, panel });\n return `${relayHttpBaseUrl.replace(/\\/$/, '')}/front_end/chii_app.html?${params.toString()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Opt-out check\n// ---------------------------------------------------------------------------\n\n/**\n * Returns `true` when auto-open is **disabled** via the `AIT_AUTO_DEVTOOLS`\n * env var. Only the explicit `\"0\"` value disables it; anything else (including\n * absent) leaves auto-open enabled.\n */\nexport function isAutoDevtoolsDisabled(): boolean {\n return process.env.AIT_AUTO_DEVTOOLS === '0';\n}\n\n// ---------------------------------------------------------------------------\n// Browser open (Node-only, sync)\n// ---------------------------------------------------------------------------\n\n/**\n * Opens the given URL in the OS default browser using a platform-appropriate\n * command. Returns `true` on success.\n *\n * Failures are silent from the caller's perspective — the caller should log\n * the URL to stderr as a fallback before calling this function.\n */\nexport function openUrlInBrowser(url: string): boolean {\n // Test hook: skip actual spawn when running in vitest / CI where the OS open\n // command may hang or be absent. Production code never sets this.\n if (process.env.AIT_AUTO_DEVTOOLS_TEST_SKIP_SPAWN === '1') return false;\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { spawnSync } = require('node:child_process') as typeof import('node:child_process');\n const platform = process.platform;\n\n type Candidate = { cmd: string; args: string[] };\n let candidates: Candidate[];\n if (platform === 'darwin') {\n candidates = [{ cmd: 'open', args: [url] }];\n } else if (platform === 'win32') {\n candidates = [{ cmd: 'cmd', args: ['/c', 'start', '', url] }];\n } else {\n // Linux + fallback\n candidates = [\n { cmd: 'xdg-open', args: [url] },\n { cmd: 'sensible-browser', args: [url] },\n { cmd: 'x-www-browser', args: [url] },\n ];\n }\n\n for (const { cmd, args } of candidates) {\n try {\n const result = spawnSync(cmd, args, { encoding: 'utf8', timeout: 5_000 });\n if (!result.error && result.status === 0) return true;\n } catch {\n // Try next candidate.\n }\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// AutoDevtoolsOpener — stateful once-per-session open guard\n// ---------------------------------------------------------------------------\n\n/**\n * Options for {@link AutoDevtoolsOpener.open}.\n *\n * The `relayHttpBaseUrl` and `targetId` fields are required to build a working\n * Chii self-hosted inspector URL. When `relayHttpBaseUrl` is absent the open\n * is skipped (no relay available yet).\n */\nexport interface DevtoolsOpenOptions {\n /**\n * Local HTTP base URL of the Chii relay, e.g. `http://127.0.0.1:9100`.\n * Used to build the `<relay-base>/front_end/chii_app.html?wss=…` URL.\n *\n * For env 3/4 (intoss relay) this is `http://127.0.0.1:<port>`.\n * For env 2 (external PWA relay) this is the relay's external HTTP URL\n * (e.g. `https://<host>.trycloudflare.com`).\n *\n * When absent or empty, `open()` is a no-op.\n *\n * SECRET-HANDLING: this value contains the relay host. Callers MUST NOT\n * log it to stdout; stderr is the intended surface.\n */\n relayHttpBaseUrl: string | null | undefined;\n /**\n * Chii target id of the attached page, from `listTargets()[0].id`.\n * When absent or empty, `open()` is a no-op.\n */\n targetId: string | null | undefined;\n /**\n * Function that mints a fresh TOTP code when called. Called at most once per\n * `open()` invocation, immediately before building the inspector URL.\n *\n * Pass `undefined` when TOTP is disabled (no `at=` param is added).\n *\n * SECRET-HANDLING: the function MUST return only the code (6 digits), not\n * the secret. The code rides in the URL's `at=` param only.\n */\n mintTotp?: () => string;\n /** Current MCP environment (`mock` | `relay`). `open()` no-ops on `mock`. */\n env: McpEnvironment;\n}\n\n/**\n * Manages auto-opening Chrome DevTools exactly once per relay attach session.\n *\n * Create one instance per `runDebugServer` call and pass its `open()` method\n * as the `onFirstAttach` callback to `startAttachWatcher`.\n *\n * The open fires at most once. Subsequent `open()` calls are no-ops.\n * Opt-out and mock-environment guard are checked at call time.\n */\nexport class AutoDevtoolsOpener {\n private _opened = false;\n\n /**\n * Attempts to auto-open Chii DevTools in the developer's browser.\n *\n * Builds a `<relay-base>/front_end/chii_app.html?wss=…` URL pointing at the\n * attached target. A fresh TOTP `at=` code is minted at call time so the\n * relay's WebSocket upgrade gate accepts the connection.\n *\n * No-op when any of the following conditions hold:\n * 1. Already opened this session (`_opened` is true).\n * 2. `AIT_AUTO_DEVTOOLS=0` opt-out is set.\n * 3. `options.env` is `mock` (env 1 — F12 is already available).\n * 4. `options.relayHttpBaseUrl` is null/undefined/empty (relay not up yet).\n * 5. `options.targetId` is null/undefined/empty (no page attached yet).\n *\n * Always writes the DevTools URL to stderr so the developer can copy it\n * if the browser open fails or the popup is blocked.\n *\n * TOTP expiry caveat: the `at=` code embedded in the URL is valid for the\n * current 30-second RFC 6238 step (±1 skew = 90 s). The developer must open\n * the URL within that window; if they miss it, reload the page or re-run\n * `open()` (though the once-per-session guard prevents that — restart the\n * MCP server if needed).\n *\n * SECRET-HANDLING: the inspector URL (written to stderr) contains the relay\n * host and a short-lived TOTP code. Do NOT write it to stdout or any\n * persistent log.\n */\n open(options: DevtoolsOpenOptions): void {\n if (this._opened) return;\n if (isAutoDevtoolsDisabled()) return;\n if (options.env === 'mock') return;\n if (!options.relayHttpBaseUrl) return;\n if (!options.targetId) return;\n\n this._opened = true;\n\n const inspectorUrl = buildChiiInspectorUrl(\n options.relayHttpBaseUrl,\n options.targetId,\n options.mintTotp,\n );\n\n process.stderr.write(\n '[ait-debug] 기기가 연결됐습니다 — Chii DevTools를 자동으로 엽니다.\\n' +\n `[ait-debug] DevTools URL: ${inspectorUrl}\\n` +\n '[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)\\n' +\n '[ait-debug] 주의: URL의 at= 코드는 30초 창 안에서만 유효합니다.\\n',\n );\n\n const opened = openUrlInBrowser(inspectorUrl);\n if (!opened) {\n process.stderr.write(\n '[ait-debug] 브라우저 자동 열기 실패 — 위 URL을 브라우저에서 직접 여세요.\\n',\n );\n }\n }\n\n /** Returns `true` if `open()` has passed all guards and fired once. */\n get opened(): boolean {\n return this._opened;\n }\n}\n"],"mappings":";;;;;;AAuJA,SAAgB,yBAAkC;AAChD,QAAO,QAAQ,IAAI,sBAAsB;;;;;;;;;AAc3C,SAAgB,iBAAiB,KAAsB;AAGrD,KAAI,QAAQ,IAAI,sCAAsC,IAAK,QAAO;CAElE,MAAM,EAAE,cAAc,QAAQ,qBAAqB;CACnD,MAAM,WAAW,QAAQ;CAGzB,IAAI;AACJ,KAAI,aAAa,SACf,cAAa,CAAC;EAAE,KAAK;EAAQ,MAAM,CAAC,IAAI;EAAE,CAAC;UAClC,aAAa,QACtB,cAAa,CAAC;EAAE,KAAK;EAAO,MAAM;GAAC;GAAM;GAAS;GAAI;GAAI;EAAE,CAAC;KAG7D,cAAa;EACX;GAAE,KAAK;GAAY,MAAM,CAAC,IAAI;GAAE;EAChC;GAAE,KAAK;GAAoB,MAAM,CAAC,IAAI;GAAE;EACxC;GAAE,KAAK;GAAiB,MAAM,CAAC,IAAI;GAAE;EACtC;AAGH,MAAK,MAAM,EAAE,KAAK,UAAU,WAC1B,KAAI;EACF,MAAM,SAAS,UAAU,KAAK,MAAM;GAAE,UAAU;GAAQ,SAAS;GAAO,CAAC;AACzE,MAAI,CAAC,OAAO,SAAS,OAAO,WAAW,EAAG,QAAO;SAC3C;AAIV,QAAO"}
|
package/dist/mcp/cli.js
CHANGED
|
@@ -812,6 +812,66 @@ var ChiiCdpConnection = class {
|
|
|
812
812
|
* predicate from the caller's perspective; this module only forwards pass/fail.
|
|
813
813
|
*/
|
|
814
814
|
const require$1 = createRequire(import.meta.url);
|
|
815
|
+
/**
|
|
816
|
+
* WS keepalive ping interval (ms).
|
|
817
|
+
*
|
|
818
|
+
* Cloudflare proxied connections are dropped after ~100 s of no traffic.
|
|
819
|
+
* 45 s comfortably fits inside that window and lets both the phone-target leg
|
|
820
|
+
* and the daemon-client leg survive idle CDP sessions.
|
|
821
|
+
*/
|
|
822
|
+
const DEFAULT_KEEPALIVE_INTERVAL_MS = 45e3;
|
|
823
|
+
/**
|
|
824
|
+
* Loads chii's internal WebSocketServer class and returns it together with a
|
|
825
|
+
* flag indicating whether the real class was found.
|
|
826
|
+
*
|
|
827
|
+
* Returns `null` if the internal path is not resolvable (future chii release
|
|
828
|
+
* changes the layout) — callers skip keepalive gracefully.
|
|
829
|
+
*/
|
|
830
|
+
function tryLoadChiiWssClass() {
|
|
831
|
+
try {
|
|
832
|
+
const mod = require$1("chii/server/lib/WebSocketServer");
|
|
833
|
+
if (typeof mod === "function") return mod;
|
|
834
|
+
} catch {}
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Calls `chii.start()` and returns the chii `WebSocketServer` instance that
|
|
839
|
+
* was constructed during the call.
|
|
840
|
+
*
|
|
841
|
+
* How: `chii/server/index.js`'s `start()` creates `new WebSocketServer()`
|
|
842
|
+
* where `WebSocketServer` is captured from `require('./lib/WebSocketServer')`
|
|
843
|
+
* at module load time. The class reference is stable, so we can temporarily
|
|
844
|
+
* patch `ChiiWssClass.prototype.start` — which runs *on the instance* —
|
|
845
|
+
* to record `this` before the original `start` runs.
|
|
846
|
+
*
|
|
847
|
+
* The patch is installed before `chii.start()` and removed (via `finally`)
|
|
848
|
+
* immediately after, so concurrent `startChiiRelay` calls nest correctly: each
|
|
849
|
+
* call's patch overrides the previous in the prototype chain for the duration
|
|
850
|
+
* of its own `chii.start()` call, restoring the prior descriptor on exit.
|
|
851
|
+
*
|
|
852
|
+
* If `ChiiWssClass` is null (internal path changed in a future chii release),
|
|
853
|
+
* `chii.start()` runs unpatched and the function returns null — callers skip
|
|
854
|
+
* keepalive gracefully without affecting relay correctness.
|
|
855
|
+
*/
|
|
856
|
+
async function startChiiWithCapture(chii, startOptions, ChiiWssClass) {
|
|
857
|
+
if (ChiiWssClass === null) {
|
|
858
|
+
await chii.start(startOptions);
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
let captured = null;
|
|
862
|
+
const proto = ChiiWssClass.prototype;
|
|
863
|
+
const originalStart = proto.start;
|
|
864
|
+
proto.start = function(server) {
|
|
865
|
+
captured = this;
|
|
866
|
+
return originalStart.call(this, server);
|
|
867
|
+
};
|
|
868
|
+
try {
|
|
869
|
+
await chii.start(startOptions);
|
|
870
|
+
} finally {
|
|
871
|
+
proto.start = originalStart;
|
|
872
|
+
}
|
|
873
|
+
return captured;
|
|
874
|
+
}
|
|
815
875
|
function loadChiiServer() {
|
|
816
876
|
const mod = require$1("chii");
|
|
817
877
|
if (typeof mod === "object" && mod !== null && "start" in mod && typeof mod.start === "function") return mod;
|
|
@@ -864,6 +924,7 @@ async function startChiiRelay(options = {}) {
|
|
|
864
924
|
const requestedPort = options.port ?? 0;
|
|
865
925
|
const host = options.host ?? "127.0.0.1";
|
|
866
926
|
const { verifyAuth, onAuthReject } = options;
|
|
927
|
+
const keepaliveIntervalMs = options.keepaliveIntervalMs !== void 0 ? options.keepaliveIntervalMs : DEFAULT_KEEPALIVE_INTERVAL_MS;
|
|
867
928
|
const httpServer = createServer();
|
|
868
929
|
const notifyAuthReject = (kind) => {
|
|
869
930
|
if (onAuthReject === void 0) return;
|
|
@@ -883,11 +944,12 @@ async function startChiiRelay(options = {}) {
|
|
|
883
944
|
notifyAuthReject("http-request");
|
|
884
945
|
}
|
|
885
946
|
});
|
|
886
|
-
|
|
947
|
+
const chiiWssClass = keepaliveIntervalMs > 0 ? tryLoadChiiWssClass() : null;
|
|
948
|
+
const capturedChiiWss = await startChiiWithCapture(loadChiiServer(), {
|
|
887
949
|
server: httpServer,
|
|
888
950
|
domain: `${host}:${requestedPort}`,
|
|
889
951
|
port: requestedPort
|
|
890
|
-
});
|
|
952
|
+
}, chiiWssClass);
|
|
891
953
|
if (verifyAuth) {
|
|
892
954
|
const chiiUpgradeListeners = httpServer.listeners("upgrade");
|
|
893
955
|
httpServer.removeAllListeners("upgrade");
|
|
@@ -912,10 +974,21 @@ async function startChiiRelay(options = {}) {
|
|
|
912
974
|
resolve(httpServer.address().port);
|
|
913
975
|
});
|
|
914
976
|
});
|
|
977
|
+
let keepaliveHandle = null;
|
|
978
|
+
if (keepaliveIntervalMs > 0 && capturedChiiWss !== null) {
|
|
979
|
+
const chiiWss = capturedChiiWss;
|
|
980
|
+
keepaliveHandle = setInterval(() => {
|
|
981
|
+
for (const client of chiiWss._wss.clients) if (client.readyState === 1) client.ping();
|
|
982
|
+
}, keepaliveIntervalMs);
|
|
983
|
+
}
|
|
915
984
|
return {
|
|
916
985
|
port: actualPort,
|
|
917
986
|
baseUrl: `http://${host}:${actualPort}`,
|
|
918
987
|
close: () => new Promise((resolve) => {
|
|
988
|
+
if (keepaliveHandle !== null) {
|
|
989
|
+
clearInterval(keepaliveHandle);
|
|
990
|
+
keepaliveHandle = null;
|
|
991
|
+
}
|
|
919
992
|
httpServer.close(() => resolve());
|
|
920
993
|
})
|
|
921
994
|
};
|
|
@@ -1074,35 +1147,65 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1074
1147
|
//#endregion
|
|
1075
1148
|
//#region src/mcp/devtools-opener.ts
|
|
1076
1149
|
/**
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
* The
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1150
|
+
* Assembles the Chii self-hosted DevTools inspector URL for a given relay
|
|
1151
|
+
* and target.
|
|
1152
|
+
*
|
|
1153
|
+
* Chii serves its own DevTools frontend at
|
|
1154
|
+
* `<relayHttpBaseUrl>/front_end/chii_app.html`. The `ws=` (plain HTTP relay)
|
|
1155
|
+
* or `wss=` (HTTPS relay) query parameter is a URL-encoded string of the form
|
|
1156
|
+
* `<relay-host>/client/<uuid>?target=<id>` (and optionally `&at=<totp>`) —
|
|
1157
|
+
* the same format used by Chii's own target list page (derived from
|
|
1158
|
+
* `chii/public/index.js`).
|
|
1159
|
+
*
|
|
1160
|
+
* The `at=` TOTP code is minted at call time via `mintTotp()`. It is valid
|
|
1161
|
+
* for the current 30-second RFC 6238 step (±1 step skew = 90 s acceptance
|
|
1162
|
+
* window). The developer must open the returned URL within that window. If
|
|
1163
|
+
* the window expires before the browser connects, the relay will reject the
|
|
1164
|
+
* WebSocket upgrade with close code 4401.
|
|
1165
|
+
*
|
|
1166
|
+
* SECRET-HANDLING: `mintTotp` returns a code, not a secret. The code is
|
|
1167
|
+
* embedded in the `wss=` parameter (inside the `at=` param) of the returned
|
|
1168
|
+
* URL. Callers MUST NOT log the returned URL to stdout (stderr is OK — it is
|
|
1169
|
+
* the intended fallback surface for the developer to copy the URL).
|
|
1170
|
+
*
|
|
1171
|
+
* @param relayHttpBaseUrl - Local HTTP base URL of the Chii relay, e.g.
|
|
1172
|
+
* `http://127.0.0.1:9100`. No trailing slash.
|
|
1173
|
+
* @param targetId - Chii target id (from `GET <relay>/targets`).
|
|
1174
|
+
* @param mintTotp - Optional function that returns a fresh 6-digit TOTP code
|
|
1175
|
+
* string. Called at most once. When omitted (TOTP disabled) no `at=` param
|
|
1176
|
+
* is added.
|
|
1094
1177
|
* @param panel - Initial panel. Defaults to `"console"`.
|
|
1095
1178
|
*
|
|
1096
1179
|
* @example
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1180
|
+
* buildChiiInspectorUrl(
|
|
1181
|
+
* 'http://127.0.0.1:9100',
|
|
1182
|
+
* 'abc123',
|
|
1183
|
+
* () => generateTotp(secret),
|
|
1184
|
+
* )
|
|
1185
|
+
* // → 'http://127.0.0.1:9100/front_end/chii_app.html?ws=127.0.0.1%3A9100%2Fclient%2F<uuid>%3Ftarget%3Dabc123%26at%3D<code>'
|
|
1186
|
+
*/
|
|
1187
|
+
function buildChiiInspectorUrl(relayHttpBaseUrl, targetId, mintTotp, panel = "console") {
|
|
1188
|
+
let relayHost;
|
|
1189
|
+
let wsParamName;
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = new URL(relayHttpBaseUrl);
|
|
1192
|
+
relayHost = parsed.host;
|
|
1193
|
+
wsParamName = parsed.protocol === "https:" ? "wss" : "ws";
|
|
1194
|
+
} catch {
|
|
1195
|
+
relayHost = relayHttpBaseUrl.replace(/^https?:\/\//i, "");
|
|
1196
|
+
wsParamName = /^https:/i.test(relayHttpBaseUrl) ? "wss" : "ws";
|
|
1197
|
+
}
|
|
1198
|
+
const clientId = `devtools-opener-${Date.now().toString(36)}`;
|
|
1199
|
+
let wsPath = `${relayHost}/client/${clientId}?target=${encodeURIComponent(targetId)}`;
|
|
1200
|
+
if (mintTotp) {
|
|
1201
|
+
const code = mintTotp();
|
|
1202
|
+
wsPath += `&at=${encodeURIComponent(code)}`;
|
|
1203
|
+
}
|
|
1204
|
+
const params = new URLSearchParams({
|
|
1205
|
+
[wsParamName]: wsPath,
|
|
1104
1206
|
panel
|
|
1105
|
-
})
|
|
1207
|
+
});
|
|
1208
|
+
return `${relayHttpBaseUrl.replace(/\/$/, "")}/front_end/chii_app.html?${params.toString()}`;
|
|
1106
1209
|
}
|
|
1107
1210
|
/**
|
|
1108
1211
|
* Returns `true` when auto-open is **disabled** via the `AIT_AUTO_DEVTOOLS`
|
|
@@ -1172,31 +1275,45 @@ function openUrlInBrowser(url) {
|
|
|
1172
1275
|
var AutoDevtoolsOpener = class {
|
|
1173
1276
|
_opened = false;
|
|
1174
1277
|
/**
|
|
1175
|
-
* Attempts to auto-open
|
|
1278
|
+
* Attempts to auto-open Chii DevTools in the developer's browser.
|
|
1279
|
+
*
|
|
1280
|
+
* Builds a `<relay-base>/front_end/chii_app.html?wss=…` URL pointing at the
|
|
1281
|
+
* attached target. A fresh TOTP `at=` code is minted at call time so the
|
|
1282
|
+
* relay's WebSocket upgrade gate accepts the connection.
|
|
1176
1283
|
*
|
|
1177
1284
|
* No-op when any of the following conditions hold:
|
|
1178
1285
|
* 1. Already opened this session (`_opened` is true).
|
|
1179
1286
|
* 2. `AIT_AUTO_DEVTOOLS=0` opt-out is set.
|
|
1180
|
-
* 3.
|
|
1181
|
-
* 4. `
|
|
1287
|
+
* 3. `options.env` is `mock` (env 1 — F12 is already available).
|
|
1288
|
+
* 4. `options.relayHttpBaseUrl` is null/undefined/empty (relay not up yet).
|
|
1289
|
+
* 5. `options.targetId` is null/undefined/empty (no page attached yet).
|
|
1182
1290
|
*
|
|
1183
1291
|
* Always writes the DevTools URL to stderr so the developer can copy it
|
|
1184
1292
|
* if the browser open fails or the popup is blocked.
|
|
1185
1293
|
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1294
|
+
* TOTP expiry caveat: the `at=` code embedded in the URL is valid for the
|
|
1295
|
+
* current 30-second RFC 6238 step (±1 skew = 90 s). The developer must open
|
|
1296
|
+
* the URL within that window; if they miss it, reload the page or re-run
|
|
1297
|
+
* `open()` (though the once-per-session guard prevents that — restart the
|
|
1298
|
+
* MCP server if needed).
|
|
1299
|
+
*
|
|
1300
|
+
* SECRET-HANDLING: the inspector URL (written to stderr) contains the relay
|
|
1301
|
+
* host and a short-lived TOTP code. Do NOT write it to stdout or any
|
|
1302
|
+
* persistent log.
|
|
1188
1303
|
*/
|
|
1189
|
-
open(
|
|
1304
|
+
open(options) {
|
|
1190
1305
|
if (this._opened) return;
|
|
1191
1306
|
if (isAutoDevtoolsDisabled()) return;
|
|
1192
|
-
if (env === "mock") return;
|
|
1193
|
-
if (!
|
|
1307
|
+
if (options.env === "mock") return;
|
|
1308
|
+
if (!options.relayHttpBaseUrl) return;
|
|
1309
|
+
if (!options.targetId) return;
|
|
1194
1310
|
this._opened = true;
|
|
1195
|
-
const
|
|
1196
|
-
process.stderr.write(`[ait-debug] 기기가 연결됐습니다 —
|
|
1197
|
-
[ait-debug]
|
|
1311
|
+
const inspectorUrl = buildChiiInspectorUrl(options.relayHttpBaseUrl, options.targetId, options.mintTotp);
|
|
1312
|
+
process.stderr.write(`[ait-debug] 기기가 연결됐습니다 — Chii DevTools를 자동으로 엽니다.
|
|
1313
|
+
[ait-debug] DevTools URL: ${inspectorUrl}\n[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)
|
|
1314
|
+
[ait-debug] 주의: URL의 at= 코드는 30초 창 안에서만 유효합니다.
|
|
1198
1315
|
`);
|
|
1199
|
-
if (!openUrlInBrowser(
|
|
1316
|
+
if (!openUrlInBrowser(inspectorUrl)) process.stderr.write("[ait-debug] 브라우저 자동 열기 실패 — 위 URL을 브라우저에서 직접 여세요.\n");
|
|
1200
1317
|
}
|
|
1201
1318
|
/** Returns `true` if `open()` has passed all guards and fired once. */
|
|
1202
1319
|
get opened() {
|
|
@@ -4449,7 +4566,7 @@ async function readMcpSdkVersion() {
|
|
|
4449
4566
|
* some test environments that skip the build step).
|
|
4450
4567
|
*/
|
|
4451
4568
|
function readDevtoolsVersion() {
|
|
4452
|
-
return "0.1.
|
|
4569
|
+
return "0.1.71";
|
|
4453
4570
|
}
|
|
4454
4571
|
/**
|
|
4455
4572
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -4953,7 +5070,7 @@ function createDebugServer(deps) {
|
|
|
4953
5070
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
4954
5071
|
const server = new Server({
|
|
4955
5072
|
name: "ait-debug",
|
|
4956
|
-
version: "0.1.
|
|
5073
|
+
version: "0.1.71"
|
|
4957
5074
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
4958
5075
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
4959
5076
|
const conn = router.active;
|
|
@@ -5709,6 +5826,7 @@ async function bootRelayFamily(options = {}) {
|
|
|
5709
5826
|
return {
|
|
5710
5827
|
connection,
|
|
5711
5828
|
relayOrigin: "intoss-webview",
|
|
5829
|
+
relayHttpUrl: relay.baseUrl,
|
|
5712
5830
|
getTunnelStatus: () => tunnelStatus,
|
|
5713
5831
|
stop() {
|
|
5714
5832
|
tunnelProbe?.stop();
|
|
@@ -5745,6 +5863,7 @@ async function bootExternalRelayFamily(relayBaseUrl) {
|
|
|
5745
5863
|
return {
|
|
5746
5864
|
connection,
|
|
5747
5865
|
relayOrigin: "external-pwa",
|
|
5866
|
+
relayHttpUrl: relayBaseUrl,
|
|
5748
5867
|
getTunnelStatus: () => tunnelStatus,
|
|
5749
5868
|
stop() {
|
|
5750
5869
|
connection.close();
|
|
@@ -5911,7 +6030,16 @@ var DualConnectionRouter = class {
|
|
|
5911
6030
|
this.attachWatcher = startAttachWatcher(activeFamily.connection, server, this.deps.attachWatcherIntervalMs ?? 1e3, () => {
|
|
5912
6031
|
this.deps.diagnosticsCollector.recordAttach();
|
|
5913
6032
|
this.deps.onPageAttach?.();
|
|
5914
|
-
if (activeFamily.connection.kind === "relay")
|
|
6033
|
+
if (activeFamily.connection.kind === "relay") {
|
|
6034
|
+
const firstTarget = activeFamily.connection.listTargets()[0];
|
|
6035
|
+
const env = deriveEnvironment(activeFamily.connection.kind, getLiveIntent(), activeFamily.relayOrigin);
|
|
6036
|
+
this.deps.devtoolsOpener.open({
|
|
6037
|
+
relayHttpBaseUrl: activeFamily.relayHttpUrl,
|
|
6038
|
+
targetId: firstTarget?.id,
|
|
6039
|
+
mintTotp: process.env.AIT_DEBUG_TOTP_SECRET ? () => generateTotp(process.env.AIT_DEBUG_TOTP_SECRET) : void 0,
|
|
6040
|
+
env
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
5915
6043
|
});
|
|
5916
6044
|
}
|
|
5917
6045
|
/**
|
|
@@ -6829,7 +6957,7 @@ function createDevServer(deps = {}) {
|
|
|
6829
6957
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
6830
6958
|
const server = new Server({
|
|
6831
6959
|
name: "ait-devtools",
|
|
6832
|
-
version: "0.1.
|
|
6960
|
+
version: "0.1.71"
|
|
6833
6961
|
}, { capabilities: { tools: {} } });
|
|
6834
6962
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
6835
6963
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|