@ait-co/devtools 0.1.71 → 0.1.73

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.
Files changed (77) hide show
  1. package/dist/{deeplink-CaO6hZVG.js → deeplink-B-94XmWA.js} +19 -3
  2. package/dist/deeplink-B-94XmWA.js.map +1 -0
  3. package/dist/{deeplink-BONXxWEO.cjs → deeplink-BLU2_hg6.cjs} +19 -3
  4. package/dist/deeplink-BLU2_hg6.cjs.map +1 -0
  5. package/dist/{deeplink-CCGiyoHq.cjs → deeplink-CU6opogq.cjs} +19 -3
  6. package/dist/deeplink-CU6opogq.cjs.map +1 -0
  7. package/dist/{deeplink-Cqli4qzm.js → deeplink-CYqDwVYs.js} +19 -3
  8. package/dist/deeplink-CYqDwVYs.js.map +1 -0
  9. package/dist/devtools-opener-BbUXBzgA.js.map +1 -1
  10. package/dist/devtools-opener-Bp671YXu.cjs.map +1 -1
  11. package/dist/devtools-opener-D84kZFtR.js.map +1 -1
  12. package/dist/devtools-opener-h6A-UjzC.cjs.map +1 -1
  13. package/dist/mcp/cli.js +75 -42
  14. package/dist/mcp/cli.js.map +1 -1
  15. package/dist/mcp/server.js +2 -2
  16. package/dist/mcp/server.js.map +1 -1
  17. package/dist/panel/index.js +18 -10
  18. package/dist/panel/index.js.map +1 -1
  19. package/dist/{qr-http-server-BIIMOcuU.cjs → qr-http-server-0uN5jxLW.cjs} +19 -11
  20. package/dist/qr-http-server-0uN5jxLW.cjs.map +1 -0
  21. package/dist/{qr-http-server-ClakYBO9.cjs → qr-http-server-BTjpFS3p.cjs} +19 -11
  22. package/dist/qr-http-server-BTjpFS3p.cjs.map +1 -0
  23. package/dist/{qr-http-server-JjGU81q7.js → qr-http-server-Ditd2ndz.js} +19 -11
  24. package/dist/qr-http-server-Ditd2ndz.js.map +1 -0
  25. package/dist/{qr-http-server-CeEzLS3g.js → qr-http-server-TQG61eI4.js} +19 -11
  26. package/dist/qr-http-server-TQG61eI4.js.map +1 -0
  27. package/dist/{relay-secret-store-CLkF8Pa0.cjs → relay-secret-store-BFOEhsLO.cjs} +2 -2
  28. package/dist/{relay-secret-store-CLkF8Pa0.cjs.map → relay-secret-store-BFOEhsLO.cjs.map} +1 -1
  29. package/dist/{relay-secret-store-DBcKWUl9.js → relay-secret-store-C_LUxvAp.js} +2 -2
  30. package/dist/{relay-secret-store-DBcKWUl9.js.map → relay-secret-store-C_LUxvAp.js.map} +1 -1
  31. package/dist/{relay-secret-store-C4QQN5NA.js → relay-secret-store-D-W-WaSx.js} +2 -2
  32. package/dist/{relay-secret-store-C4QQN5NA.js.map → relay-secret-store-D-W-WaSx.js.map} +1 -1
  33. package/dist/{relay-url-store-COG2dSql.cjs → relay-url-store-2sy_l2bf.cjs} +2 -2
  34. package/dist/{relay-url-store-COG2dSql.cjs.map → relay-url-store-2sy_l2bf.cjs.map} +1 -1
  35. package/dist/{relay-url-store-WKfo0VQV.js → relay-url-store-DAh5KiJi.js} +2 -2
  36. package/dist/{relay-url-store-WKfo0VQV.js.map → relay-url-store-DAh5KiJi.js.map} +1 -1
  37. package/dist/{relay-url-store-Dq3vpd95.js → relay-url-store-DjKJJZ0d.js} +2 -2
  38. package/dist/{relay-url-store-Dq3vpd95.js.map → relay-url-store-DjKJJZ0d.js.map} +1 -1
  39. package/dist/{totp-D0a8VwoR.js → totp-BfVk8gQe.js} +6 -3
  40. package/dist/{totp-D0a8VwoR.js.map → totp-BfVk8gQe.js.map} +1 -1
  41. package/dist/totp-BxtxuEt4.js.map +1 -1
  42. package/dist/{totp-DA8vjAi7.cjs → totp-D4iTMA9U.cjs} +6 -3
  43. package/dist/{totp-DA8vjAi7.cjs.map → totp-D4iTMA9U.cjs.map} +1 -1
  44. package/dist/totp-D8f6qAEu.js +3 -0
  45. package/dist/totp-D9rndqg_.cjs.map +1 -1
  46. package/dist/{totp-BjtKFt88.js → totp-DbEfKQRi.js} +6 -3
  47. package/dist/{totp-BjtKFt88.js.map → totp-DbEfKQRi.js.map} +1 -1
  48. package/dist/{tunnel-DwVrcZ56.cjs → tunnel-BXAWl2tI.cjs} +22 -11
  49. package/dist/tunnel-BXAWl2tI.cjs.map +1 -0
  50. package/dist/{tunnel-aIy_7nWm.js → tunnel-BxGnLAat.js} +22 -11
  51. package/dist/tunnel-BxGnLAat.js.map +1 -0
  52. package/dist/unplugin/index.cjs +17 -7
  53. package/dist/unplugin/index.cjs.map +1 -1
  54. package/dist/unplugin/index.d.cts.map +1 -1
  55. package/dist/unplugin/index.d.ts.map +1 -1
  56. package/dist/unplugin/index.js +17 -7
  57. package/dist/unplugin/index.js.map +1 -1
  58. package/dist/unplugin/tunnel.cjs +20 -9
  59. package/dist/unplugin/tunnel.cjs.map +1 -1
  60. package/dist/unplugin/tunnel.d.cts +36 -3
  61. package/dist/unplugin/tunnel.d.cts.map +1 -1
  62. package/dist/unplugin/tunnel.d.ts +36 -3
  63. package/dist/unplugin/tunnel.d.ts.map +1 -1
  64. package/dist/unplugin/tunnel.js +20 -9
  65. package/dist/unplugin/tunnel.js.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/deeplink-BONXxWEO.cjs.map +0 -1
  68. package/dist/deeplink-CCGiyoHq.cjs.map +0 -1
  69. package/dist/deeplink-CaO6hZVG.js.map +0 -1
  70. package/dist/deeplink-Cqli4qzm.js.map +0 -1
  71. package/dist/qr-http-server-BIIMOcuU.cjs.map +0 -1
  72. package/dist/qr-http-server-CeEzLS3g.js.map +0 -1
  73. package/dist/qr-http-server-ClakYBO9.cjs.map +0 -1
  74. package/dist/qr-http-server-JjGU81q7.js.map +0 -1
  75. package/dist/totp-CQFmgOhM.js +0 -3
  76. package/dist/tunnel-DwVrcZ56.cjs.map +0 -1
  77. package/dist/tunnel-aIy_7nWm.js.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. 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"}
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 ~3 minutes (the relay gate\n * accepts ±RELAY_VERIFY_SKEW_STEPS=6 steps = 180–210 s). If the developer\n * does not open the URL within that window the WebSocket upgrade will be\n * rejected with 4401. In practice the browser opens immediately after the OS\n * `open` command; if needed the developer can copy the wss= param, replace\n * `at=`, and 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 ~3 minutes (relay gate accepts ±RELAY_VERIFY_SKEW_STEPS=6 steps =\n * 180–210 s). The developer must open the returned URL within that window.\n * If 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 ~3\n * minutes (relay gate ±RELAY_VERIFY_SKEW_STEPS=6 steps = 180–210 s). The\n * developer must open the URL within that window; if they miss it, reload\n * the page or re-run `open()` (though the once-per-session guard prevents\n * that — restart the 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= 코드는 ~3분 안에서만 유효합니다.\\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":";;;;;;AAsJA,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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { i as generateTotp, n as assertRelayAuthConfigured, r as buildRelayVerifyAuth } from "../totp-D0a8VwoR.js";
3
- import { t as loadRelaySecretReadOnly } from "../relay-secret-store-DBcKWUl9.js";
2
+ import { i as generateTotp, n as assertRelayAuthConfigured, r as buildRelayVerifyAuth } from "../totp-BfVk8gQe.js";
3
+ import { t as loadRelaySecretReadOnly } from "../relay-secret-store-C_LUxvAp.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { argv } from "node:process";
@@ -1012,6 +1012,11 @@ const LAUNCHER_URL = "https://devtools.aitc.dev/launcher/";
1012
1012
  * is injected. `&at=<totpCode>` is added only when a code is provided (same
1013
1013
  * conditional as {@link buildDeepLinkAttachUrl}).
1014
1014
  *
1015
+ * When `opts.name` is given (non-blank), it is added as `&name=` so the
1016
+ * launcher partner bar shows the app name instead of the generic default (#498).
1017
+ * When `opts.icon` is an absolute https:// URL, it is added as `&icon=` so the
1018
+ * launcher can render an icon next to the title (#498).
1019
+ *
1015
1020
  * Unlike `buildDeepLinkAttachUrl` (which splices onto a non-special scheme URL
1016
1021
  * via raw string manipulation), this function uses WHATWG `encodeURIComponent`
1017
1022
  * because the target is a standard `https:` URL.
@@ -1026,12 +1031,23 @@ const LAUNCHER_URL = "https://devtools.aitc.dev/launcher/";
1026
1031
  * @param totpCode - Optional current TOTP code (6 digits). When provided, it
1027
1032
  * is appended as `at=<totpCode>`. Must be computed at call time — it rotates
1028
1033
  * every 30 s. Omit when TOTP is disabled.
1034
+ * @param opts - Optional app identity hints: `name` and `icon` (#498).
1029
1035
  * @returns The launcher deep-link URL with `?url=<enc>&debug=1&relay=<enc>
1030
- * [&at=<code>]` params.
1036
+ * [&at=<code>][&name=<enc>][&icon=<enc>]` params.
1031
1037
  */
1032
- function buildLauncherAttachUrl(tunnelUrl, wssUrl, totpCode) {
1038
+ function buildLauncherAttachUrl(tunnelUrl, wssUrl, totpCode, opts) {
1033
1039
  let url = `${LAUNCHER_URL}?url=${encodeURIComponent(tunnelUrl)}&debug=1&relay=${encodeURIComponent(wssUrl)}`;
1034
1040
  if (totpCode !== void 0 && totpCode !== "") url += `&at=${encodeURIComponent(totpCode)}`;
1041
+ if (opts?.name !== void 0 && opts.name.trim() !== "") url += `&name=${encodeURIComponent(opts.name.trim())}`;
1042
+ if (opts?.icon !== void 0) {
1043
+ let iconParsed;
1044
+ try {
1045
+ iconParsed = new URL(opts.icon);
1046
+ } catch {
1047
+ iconParsed = null;
1048
+ }
1049
+ if (iconParsed?.protocol === "https:") url += `&icon=${encodeURIComponent(opts.icon)}`;
1050
+ }
1035
1051
  return url;
1036
1052
  }
1037
1053
  /**
@@ -1158,9 +1174,9 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
1158
1174
  * `chii/public/index.js`).
1159
1175
  *
1160
1176
  * 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
1177
+ * for ~3 minutes (relay gate accepts ±RELAY_VERIFY_SKEW_STEPS=6 steps =
1178
+ * 180–210 s). The developer must open the returned URL within that window.
1179
+ * If the window expires before the browser connects, the relay will reject the
1164
1180
  * WebSocket upgrade with close code 4401.
1165
1181
  *
1166
1182
  * SECRET-HANDLING: `mintTotp` returns a code, not a secret. The code is
@@ -1291,11 +1307,11 @@ var AutoDevtoolsOpener = class {
1291
1307
  * Always writes the DevTools URL to stderr so the developer can copy it
1292
1308
  * if the browser open fails or the popup is blocked.
1293
1309
  *
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).
1310
+ * TOTP expiry caveat: the `at=` code embedded in the URL is valid for ~3
1311
+ * minutes (relay gate ±RELAY_VERIFY_SKEW_STEPS=6 steps = 180–210 s). The
1312
+ * developer must open the URL within that window; if they miss it, reload
1313
+ * the page or re-run `open()` (though the once-per-session guard prevents
1314
+ * that — restart the MCP server if needed).
1299
1315
  *
1300
1316
  * SECRET-HANDLING: the inspector URL (written to stderr) contains the relay
1301
1317
  * host and a short-lived TOTP code. Do NOT write it to stdout or any
@@ -1311,7 +1327,7 @@ var AutoDevtoolsOpener = class {
1311
1327
  const inspectorUrl = buildChiiInspectorUrl(options.relayHttpBaseUrl, options.targetId, options.mintTotp);
1312
1328
  process.stderr.write(`[ait-debug] 기기가 연결됐습니다 — Chii DevTools를 자동으로 엽니다.
1313
1329
  [ait-debug] DevTools URL: ${inspectorUrl}\n[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)
1314
- [ait-debug] 주의: URL의 at= 코드는 30초 안에서만 유효합니다.
1330
+ [ait-debug] 주의: URL의 at= 코드는 ~3분 안에서만 유효합니다.
1315
1331
  `);
1316
1332
  if (!openUrlInBrowser(inspectorUrl)) process.stderr.write("[ait-debug] 브라우저 자동 열기 실패 — 위 URL을 브라우저에서 직접 여세요.\n");
1317
1333
  }
@@ -2093,7 +2109,7 @@ const en = {
2093
2109
  "attach.sandbox.step3": "The mini-app opens fullscreen and the debug session attaches automatically.",
2094
2110
  "attach.sandbox.faq.notInstalled": "<strong>Launcher is not installed</strong> — open <code>devtools.aitc.dev/launcher/</code> once and add it to your home screen",
2095
2111
  "attach.sandbox.faq.cameraApp": "<strong>Scanning with the camera app opens a Safari tab (bottom tab bar visible)</strong> — relaunch from the launcher icon and use the in-app scanner",
2096
- "attach.sandbox.faq.totp": "<strong>QR expired (TOTP 30 s)</strong> — scan a fresh QR code",
2112
+ "attach.sandbox.faq.totp": "<strong>QR expired (TOTP ~3 min)</strong> — scan a fresh QR code",
2097
2113
  "attach.sandbox.faq.chii": "<strong>Chii injection failure / console is empty</strong> — verify the mini-app bundle has an <code>in-app</code> debug import",
2098
2114
  "attach.intoss.step1": "Open the Toss app.",
2099
2115
  "attach.intoss.step2": "Scan the QR code with your phone camera app.",
@@ -2110,7 +2126,6 @@ const en = {
2110
2126
  "launcher.urlPlaceholder": "https://example.trycloudflare.com",
2111
2127
  "launcher.openBtn": "Open",
2112
2128
  "launcher.scanBtn": "Scan QR with camera",
2113
- "launcher.rescanBtn": "Rescan",
2114
2129
  "launcher.noCamera": "No camera available — paste the URL instead.",
2115
2130
  "launcher.cameraError": "Could not access the camera — paste the URL instead.",
2116
2131
  "launcher.invalidUrlHttps": "Enter a valid https:// URL (the tunnel URL from your terminal).",
@@ -2119,11 +2134,16 @@ const en = {
2119
2134
  "launcher.debugAuthFailedHint": "The QR code may have expired. Scan a fresh QR code.",
2120
2135
  "launcher.debugAuthExpiredHint": "The debug session has expired. Scan a fresh QR from the attach page on your Mac.",
2121
2136
  "launcher.debugAuthRescanCta": "Scan a new QR",
2122
- "launcher.diagFab": "Diag",
2123
2137
  "launcher.diagTitle": "Viewport diagnostics",
2124
2138
  "launcher.diagYes": "yes",
2125
2139
  "launcher.diagNo": "no",
2126
- "launcher.letterboxDetected": "Display area is {pt}pt short — likely an iOS standalone letterbox. Removing and re-adding the launcher to the home screen may fix it."
2140
+ "launcher.letterboxDetected": "Display area is {pt}pt short — likely an iOS standalone letterbox. Removing and re-adding the launcher to the home screen may fix it.",
2141
+ "launcher.navbar.defaultTitle": "Mini App",
2142
+ "launcher.navbar.menu": "Menu",
2143
+ "launcher.navbar.close": "Close",
2144
+ "launcher.navbar.menuRescan": "Rescan",
2145
+ "launcher.navbar.menuDiag": "Viewport diagnostics",
2146
+ "launcher.navbar.menuLanguage": "Language"
2127
2147
  };
2128
2148
  //#endregion
2129
2149
  //#region src/i18n/index.ts
@@ -2337,7 +2357,7 @@ const tables = {
2337
2357
  "attach.sandbox.step3": "미니앱이 풀스크린으로 열리고 디버그 세션이 자동으로 attach됩니다.",
2338
2358
  "attach.sandbox.faq.notInstalled": "<strong>launcher가 설치돼 있지 않은 경우</strong> — <code>devtools.aitc.dev/launcher/</code>를 한 번 열어 홈 화면에 추가하세요",
2339
2359
  "attach.sandbox.faq.cameraApp": "<strong>카메라 앱으로 스캔하면 Safari 탭으로 열립니다 (하단 탭 바 노출)</strong> — launcher 아이콘으로 다시 실행해 인앱 스캔을 사용하세요",
2340
- "attach.sandbox.faq.totp": "<strong>QR이 만료된 경우 (TOTP 30초)</strong> — 새 QR을 다시 스캔하세요",
2360
+ "attach.sandbox.faq.totp": "<strong>QR이 만료된 경우 (TOTP ~3분)</strong> — 새 QR을 다시 스캔하세요",
2341
2361
  "attach.sandbox.faq.chii": "<strong>Chii 주입 실패 / 콘솔이 비어 있는 경우</strong> — 미니앱 번들에 <code>in-app</code> debug import가 있는지 확인",
2342
2362
  "attach.intoss.step1": "토스 앱을 실행하세요.",
2343
2363
  "attach.intoss.step2": "폰 카메라 앱으로 QR 코드를 스캔하세요.",
@@ -2354,7 +2374,6 @@ const tables = {
2354
2374
  "launcher.urlPlaceholder": "https://example.trycloudflare.com",
2355
2375
  "launcher.openBtn": "Open",
2356
2376
  "launcher.scanBtn": "QR 카메라로 스캔",
2357
- "launcher.rescanBtn": "Rescan",
2358
2377
  "launcher.noCamera": "카메라를 사용할 수 없습니다 — URL을 직접 붙여넣으세요.",
2359
2378
  "launcher.cameraError": "카메라에 접근할 수 없습니다 — URL을 직접 붙여넣으세요.",
2360
2379
  "launcher.invalidUrlHttps": "올바른 https:// URL을 입력하세요 (터미널의 터널 URL).",
@@ -2363,11 +2382,16 @@ const tables = {
2363
2382
  "launcher.debugAuthFailedHint": "QR 코드가 만료되었을 수 있어요. 새 QR을 다시 스캔하세요.",
2364
2383
  "launcher.debugAuthExpiredHint": "디버그 세션이 만료됐어요. Mac의 attach 페이지에서 새 QR을 스캔하세요.",
2365
2384
  "launcher.debugAuthRescanCta": "새 QR 스캔하기",
2366
- "launcher.diagFab": "진단",
2367
2385
  "launcher.diagTitle": "뷰포트 진단",
2368
2386
  "launcher.diagYes": "예",
2369
2387
  "launcher.diagNo": "아니요",
2370
- "launcher.letterboxDetected": "표시 영역이 {pt}pt 부족합니다 — iOS standalone letterbox로 보입니다. 런처를 홈 화면에서 제거 후 다시 설치하면 해소될 수 있어요."
2388
+ "launcher.letterboxDetected": "표시 영역이 {pt}pt 부족합니다 — iOS standalone letterbox로 보입니다. 런처를 홈 화면에서 제거 후 다시 설치하면 해소될 수 있어요.",
2389
+ "launcher.navbar.defaultTitle": "미니앱",
2390
+ "launcher.navbar.menu": "메뉴",
2391
+ "launcher.navbar.close": "닫기",
2392
+ "launcher.navbar.menuRescan": "다시 스캔",
2393
+ "launcher.navbar.menuDiag": "뷰포트 진단",
2394
+ "launcher.navbar.menuLanguage": "언어"
2371
2395
  },
2372
2396
  en
2373
2397
  };
@@ -2514,7 +2538,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0.5rem 0;
2514
2538
  .lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
2515
2539
  .lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
2516
2540
  .lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
2517
- </style></head><body><h1>AIT 디버그 세션 — QR 스캔</h1>__MODE_LABEL____LANG_SWITCHER__<div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>스캔 절차</h2><ol><li>홈 화면의 launcher PWA 아이콘으로 실행하세요 (Safari 주소창이 보이면 standalone이 아닙니다).</li><li>launcher 안의 <strong>"QR 카메라로 스캔"</strong>으로 이 QR 코드를 스캔하세요.</li><li>미니앱이 풀스크린으로 열리고 디버그 세션이 자동으로 attach됩니다.</li></ol></section><hr/><section><h2>진단 체크리스트</h2><ul><li><strong>launcher가 설치돼 있지 않은 경우</strong> — <code>devtools.aitc.dev/launcher/</code>를 한 번 열어 홈 화면에 추가하세요</li><li><strong>카메라 앱으로 스캔하면 Safari 탭으로 열립니다 (하단 탭 바 노출)</strong> — launcher 아이콘으로 다시 실행해 인앱 스캔을 사용하세요</li><li><strong>QR이 만료된 경우 (TOTP 30초)</strong> — 새 QR을 다시 스캔하세요</li><li><strong>Chii 주입 실패 / 콘솔이 비어 있는 경우</strong> — 미니앱 번들에 <code>in-app</code> debug import가 있는지 확인</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="복사">복사</button></div></section></body></html>`;
2541
+ </style></head><body><h1>AIT 디버그 세션 — QR 스캔</h1>__MODE_LABEL____LANG_SWITCHER__<div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>스캔 절차</h2><ol><li>홈 화면의 launcher PWA 아이콘으로 실행하세요 (Safari 주소창이 보이면 standalone이 아닙니다).</li><li>launcher 안의 <strong>"QR 카메라로 스캔"</strong>으로 이 QR 코드를 스캔하세요.</li><li>미니앱이 풀스크린으로 열리고 디버그 세션이 자동으로 attach됩니다.</li></ol></section><hr/><section><h2>진단 체크리스트</h2><ul><li><strong>launcher가 설치돼 있지 않은 경우</strong> — <code>devtools.aitc.dev/launcher/</code>를 한 번 열어 홈 화면에 추가하세요</li><li><strong>카메라 앱으로 스캔하면 Safari 탭으로 열립니다 (하단 탭 바 노출)</strong> — launcher 아이콘으로 다시 실행해 인앱 스캔을 사용하세요</li><li><strong>QR이 만료된 경우 (TOTP ~3분)</strong> — 새 QR을 다시 스캔하세요</li><li><strong>Chii 주입 실패 / 콘솔이 비어 있는 경우</strong> — 미니앱 번들에 <code>in-app</code> debug import가 있는지 확인</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="복사">복사</button></div></section></body></html>`;
2518
2542
  const attachChromeHtmlKoIntoss = `<!DOCTYPE html>
2519
2543
  <html lang="ko"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="__QR_DATA_URL__"/><title>AIT 디버그 세션 — QR 스캔</title><style>
2520
2544
  *, *::before, *::after { box-sizing: border-box; }
@@ -2666,7 +2690,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0.5rem 0;
2666
2690
  .lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
2667
2691
  .lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
2668
2692
  .lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
2669
- </style></head><body><h1>AIT Debug Session — QR Scan</h1>__MODE_LABEL____LANG_SWITCHER__<div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>How to scan</h2><ol><li>Launch the launcher PWA icon on your home screen (if the Safari address bar is visible, it is not standalone).</li><li>Scan this QR code with <strong>"Scan QR with camera"</strong> inside the launcher.</li><li>The mini-app opens fullscreen and the debug session attaches automatically.</li></ol></section><hr/><section><h2>Troubleshooting checklist</h2><ul><li><strong>Launcher is not installed</strong> — open <code>devtools.aitc.dev/launcher/</code> once and add it to your home screen</li><li><strong>Scanning with the camera app opens a Safari tab (bottom tab bar visible)</strong> — relaunch from the launcher icon and use the in-app scanner</li><li><strong>QR expired (TOTP 30 s)</strong> — scan a fresh QR code</li><li><strong>Chii injection failure / console is empty</strong> — verify the mini-app bundle has an <code>in-app</code> debug import</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="Copy">Copy</button></div></section></body></html>`;
2693
+ </style></head><body><h1>AIT Debug Session — QR Scan</h1>__MODE_LABEL____LANG_SWITCHER__<div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>How to scan</h2><ol><li>Launch the launcher PWA icon on your home screen (if the Safari address bar is visible, it is not standalone).</li><li>Scan this QR code with <strong>"Scan QR with camera"</strong> inside the launcher.</li><li>The mini-app opens fullscreen and the debug session attaches automatically.</li></ol></section><hr/><section><h2>Troubleshooting checklist</h2><ul><li><strong>Launcher is not installed</strong> — open <code>devtools.aitc.dev/launcher/</code> once and add it to your home screen</li><li><strong>Scanning with the camera app opens a Safari tab (bottom tab bar visible)</strong> — relaunch from the launcher icon and use the in-app scanner</li><li><strong>QR expired (TOTP ~3 min)</strong> — scan a fresh QR code</li><li><strong>Chii injection failure / console is empty</strong> — verify the mini-app bundle has an <code>in-app</code> debug import</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="Copy">Copy</button></div></section></body></html>`;
2670
2694
  const attachChromeHtmlEnIntoss = `<!DOCTYPE html>
2671
2695
  <html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="__QR_DATA_URL__"/><title>AIT Debug Session — QR Scan</title><style>
2672
2696
  *, *::before, *::after { box-sizing: border-box; }
@@ -3552,7 +3576,7 @@ const DEBUG_TOOL_DEFINITIONS = [
3552
3576
  },
3553
3577
  {
3554
3578
  name: "build_attach_url",
3555
- description: "The tool result already shows the QR to the user directly (Claude Code renders MCP tool output to the user's screen; they press Ctrl+O to expand if it's collapsed). Do NOT re-print or re-render the QR in your reply — that just wastes output tokens. Simply tell the user to scan the QR shown in this tool's output with their phone camera. Builds a self-attaching deep link for the active relay environment and returns a QR code. Scan the QR with the phone camera to open the mini-app and attach it to this debug session (QR is the single entry path — no USB cable or platform CLI needed). Call list_pages first to confirm the relay/tunnel is up. If the tunnel is not up, restart: `npx @ait-co/devtools devtools-mcp`.\n\nEnvironment-specific behaviour:\n • env 3 / relay-staging (start_debug mode=\"relay-staging\"): requires scheme_url — the intoss-private://…?_deploymentId=<uuid> URL from `ait deploy --scheme-only`. Splices debug=1 + relay URL into the scheme URL to produce a self-attach deep link.\n • env 2 / relay-sandbox (start_debug mode=\"relay-sandbox\"): scheme_url is NOT used. Instead, reads AIT_TUNNEL_BASE_URL (the https://*.trycloudflare.com app tunnel from `tunnel:{cdp:true}`) and builds a launcher PWA deep-link (https://devtools.aitc.dev/launcher/?url=…&debug=1&relay=…). Scan the QR with the phone to open the launcher, which frames the tunnel URL and attaches CDP.\n\nSet wait_for_attach=true to block until a page attaches (polls up to 30 s). On timeout, call build_attach_url again to resume polling. When open_in_browser=true (default), saves the QR as a PNG and opens it in the OS default browser — only works when the MCP server runs on a local GUI machine (not headless/remote containers). \n\nTOTP auth: when AIT_DEBUG_TOTP_SECRET is set on the MCP server, the returned attachUrl automatically includes the current one-time code (at=<code>) the URL is single-use for that 30-second step. The response includes a `totp` field with `expiresAt` (ISO timestamp). If the phone scan happens after expiresAt, the relay will reject the code — just call build_attach_url again to get a fresh one-time URL. Without AIT_DEBUG_TOTP_SECRET, the attachUrl has no expiry.",
3579
+ description: "The tool result already shows the QR to the user directly (Claude Code renders MCP tool output to the user's screen; they press Ctrl+O to expand if it's collapsed). Do NOT re-print or re-render the QR in your reply — that just wastes output tokens. Simply tell the user to scan the QR shown in this tool's output with their phone camera. Builds a self-attaching deep link for the active relay environment and returns a QR code. Scan the QR with the phone camera to open the mini-app and attach it to this debug session (QR is the single entry path — no USB cable or platform CLI needed). Call list_pages first to confirm the relay/tunnel is up. If the tunnel is not up, restart: `npx @ait-co/devtools devtools-mcp`.\n\nEnvironment-specific behaviour:\n • env 3 / relay-staging (start_debug mode=\"relay-staging\"): requires scheme_url — the intoss-private://…?_deploymentId=<uuid> URL from `ait deploy --scheme-only`. Splices debug=1 + relay URL into the scheme URL to produce a self-attach deep link.\n • env 2 / relay-sandbox (start_debug mode=\"relay-sandbox\"): scheme_url is NOT used. Instead, reads AIT_TUNNEL_BASE_URL (the https://*.trycloudflare.com app tunnel from `tunnel:{cdp:true}`) and builds a launcher PWA deep-link (https://devtools.aitc.dev/launcher/?url=…&debug=1&relay=…). When projectRoot is given, the app name from <projectRoot>/package.json is automatically added as name= so the launcher partner bar shows it. Scan the QR with the phone to open the launcher, which frames the tunnel URL and attaches CDP.\n\nSet wait_for_attach=true to block until a page attaches (polls up to 30 s). On timeout, call build_attach_url again to resume polling. When open_in_browser=true (default), saves the QR as a PNG and opens it in the OS default browser — only works when the MCP server runs on a local GUI machine (not headless/remote containers). \n\nTOTP auth: when AIT_DEBUG_TOTP_SECRET is set on the MCP server, the returned attachUrl automatically includes the current one-time code (at=<code>). The code is valid for ~3 minutes (the relay gate accepts ±6 TOTP steps = 180–210 s of backwards acceptance). The response includes a `totp` field with `expiresAt` (ISO timestamp, ~3 min from issuance). If the phone scan happens after expiresAt, the relay will reject the code — just call build_attach_url again to get a fresh URL. Without AIT_DEBUG_TOTP_SECRET, the attachUrl has no expiry.",
3556
3580
  inputSchema: {
3557
3581
  type: "object",
3558
3582
  properties: {
@@ -3904,10 +3928,10 @@ function listPages(connection, tunnel) {
3904
3928
  * relay URL to splice in) — the caller surfaces that as a tool error.
3905
3929
  *
3906
3930
  * When `AIT_DEBUG_TOTP_SECRET` is set, generates the current TOTP code and
3907
- * splices it as `at=<code>` into the attach URL. The code is valid for one
3908
- * 30-second time step (±1 skew accepted by the relay, so the effective window
3909
- * is up to 90 s). If the scan happens after `totp.expiresAt`, call
3910
- * `build_attach_url` again to get a fresh code.
3931
+ * splices it as `at=<code>` into the attach URL. The code is valid for ~3
3932
+ * minutes (the relay gate uses {@link RELAY_VERIFY_SKEW_STEPS}=6, accepting
3933
+ * past 6 steps = 180–210 s backwards from issuance). If the scan happens after
3934
+ * `totp.expiresAt`, call `build_attach_url` again to get a fresh code (#490).
3911
3935
  *
3912
3936
  * Also validates the scheme URL's authority. A suspicious authority (empty,
3913
3937
  * "web", "localhost", etc.) is surfaced as a non-fatal `authorityWarning` on
@@ -3935,10 +3959,10 @@ function buildAttachUrl(schemeUrl, tunnel, totpSecret) {
3935
3959
  const now = Date.now();
3936
3960
  totpCode = generateTotp(totpSecret, now);
3937
3961
  const STEP_SECONDS = 30;
3938
- const expiresAtMs = (Math.floor(now / 1e3 / STEP_SECONDS) + 1) * STEP_SECONDS * 1e3;
3962
+ const expiresAtMs = now + 6 * STEP_SECONDS * 1e3;
3939
3963
  totpMeta = {
3940
3964
  enabled: true,
3941
- ttlSeconds: STEP_SECONDS,
3965
+ ttlSeconds: 6 * STEP_SECONDS,
3942
3966
  expiresAt: new Date(expiresAtMs).toISOString()
3943
3967
  };
3944
3968
  }
@@ -4566,7 +4590,7 @@ async function readMcpSdkVersion() {
4566
4590
  * some test environments that skip the build step).
4567
4591
  */
4568
4592
  function readDevtoolsVersion() {
4569
- return "0.1.71";
4593
+ return "0.1.73";
4570
4594
  }
4571
4595
  /**
4572
4596
  * Derives the next recommended action from a completed diagnostics snapshot.
@@ -4602,7 +4626,7 @@ function computeNextRecommendedAction(tunnel, pages, env, authRejects = null) {
4602
4626
  };
4603
4627
  if (authRejects !== null && authRejects.count > 0 && pages !== null && pages.pages.length === 0) return {
4604
4628
  tool: "build_attach_url",
4605
- reason: `relay 인증(TOTP) 거부 ${authRejects.count}건 발생 (last ${authRejects.lastAt ?? "unknown"}) — QR을 다시 스캔해 새 코드로 attach하세요(코드는 30초 주기로 만료). 반복되면 폰 페이지 URL에 at 파라미터가 전달되는지(target-side TOTP 전달 경로)를 확인하세요`
4629
+ reason: `relay 인증(TOTP) 거부 ${authRejects.count}건 발생 (last ${authRejects.lastAt ?? "unknown"}) — QR을 다시 스캔해 새 코드로 attach하세요(코드는 ~3분마다 만료). 반복되면 폰 페이지 URL에 at 파라미터가 전달되는지(target-side TOTP 전달 경로)를 확인하세요`
4606
4630
  };
4607
4631
  if (isRelayEnv(env) && pages !== null && pages.pages.length === 0 && !pages.crashDetectedAt) return {
4608
4632
  tool: "build_attach_url",
@@ -5070,7 +5094,7 @@ function createDebugServer(deps) {
5070
5094
  const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
5071
5095
  const server = new Server({
5072
5096
  name: "ait-debug",
5073
- version: "0.1.71"
5097
+ version: "0.1.73"
5074
5098
  }, { capabilities: { tools: { listChanged: true } } });
5075
5099
  server.setRequestHandler(ListToolsRequestSchema, () => {
5076
5100
  const conn = router.active;
@@ -5149,7 +5173,7 @@ function createDebugServer(deps) {
5149
5173
  const buildProjectRoot = typeof rawBuildProjectRoot === "string" ? rawBuildProjectRoot : void 0;
5150
5174
  let tunnelHttpUrl = process.env.AIT_TUNNEL_BASE_URL?.trim() ?? "";
5151
5175
  if (tunnelHttpUrl === "" && buildProjectRoot !== void 0) {
5152
- const { readRelayUrls } = await import("../relay-url-store-Dq3vpd95.js");
5176
+ const { readRelayUrls } = await import("../relay-url-store-DjKJJZ0d.js");
5153
5177
  tunnelHttpUrl = (await readRelayUrls({ projectRoot: buildProjectRoot }))?.tunnelBaseUrl ?? "";
5154
5178
  }
5155
5179
  if (tunnelHttpUrl === "") return mcpError("build_attach_url(mobile): AIT_TUNNEL_BASE_URL이 설정되지 않았습니다. dev 서버가 tunnel:{cdp:true}로 기동 중이면 .ait_urls 파일이 자동 생성돼 있어야 합니다. 자동 발견이 되지 않을 경우 앱 HTTP 터널 URL을 AIT_TUNNEL_BASE_URL 환경변수로 직접 전달하세요.");
@@ -5163,18 +5187,27 @@ function createDebugServer(deps) {
5163
5187
  const now = Date.now();
5164
5188
  totpCode = generateTotp(secret, now);
5165
5189
  const STEP_SECONDS = 30;
5166
- const currentStep = Math.floor(now / 1e3 / STEP_SECONDS);
5190
+ const expiresAtMs = now + 6 * STEP_SECONDS * 1e3;
5167
5191
  totpMeta = {
5168
5192
  enabled: true,
5169
- ttlSeconds: STEP_SECONDS,
5170
- expiresAt: (/* @__PURE__ */ new Date((currentStep + 1) * STEP_SECONDS * 1e3)).toISOString()
5193
+ ttlSeconds: 6 * STEP_SECONDS,
5194
+ expiresAt: new Date(expiresAtMs).toISOString()
5171
5195
  };
5172
5196
  }
5173
- const attachUrl = buildLauncherAttachUrl(tunnelHttpUrl, tunnelStatus.wssUrl, totpCode);
5197
+ let launcherAppName;
5198
+ if (buildProjectRoot !== void 0) try {
5199
+ const { readFileSync } = await import("node:fs");
5200
+ const pkgRaw = readFileSync(`${buildProjectRoot}/package.json`, "utf8");
5201
+ const pkg = JSON.parse(pkgRaw);
5202
+ const rawName = typeof pkg.name === "string" ? pkg.name : "";
5203
+ launcherAppName = (rawName.includes("/") ? rawName.slice(rawName.indexOf("/") + 1) : rawName).trim() || void 0;
5204
+ } catch {}
5205
+ const attachUrl = buildLauncherAttachUrl(tunnelHttpUrl, tunnelStatus.wssUrl, totpCode, { name: launcherAppName });
5174
5206
  onAttachUrlBuilt?.({
5175
5207
  kind: "launcher",
5176
5208
  tunnelHttpUrl,
5177
- wssUrl: tunnelStatus.wssUrl
5209
+ wssUrl: tunnelStatus.wssUrl,
5210
+ appName: launcherAppName
5178
5211
  });
5179
5212
  const relayUrl = tunnelStatus.wssUrl;
5180
5213
  const totp = totpMeta;
@@ -5610,7 +5643,7 @@ function makeSingleConnectionRouter(connection) {
5610
5643
  function rebuildAttachUrl(parts) {
5611
5644
  const secret = process.env.AIT_DEBUG_TOTP_SECRET;
5612
5645
  const code = secret ? generateTotp(secret) : void 0;
5613
- return parts.kind === "launcher" ? buildLauncherAttachUrl(parts.tunnelHttpUrl, parts.wssUrl, code) : buildDeepLinkAttachUrl(parts.schemeUrl, parts.wssUrl, code);
5646
+ return parts.kind === "launcher" ? buildLauncherAttachUrl(parts.tunnelHttpUrl, parts.wssUrl, code, { name: parts.appName }) : buildDeepLinkAttachUrl(parts.schemeUrl, parts.wssUrl, code);
5614
5647
  }
5615
5648
  function jsonResult$1(value) {
5616
5649
  return { content: [{
@@ -5904,7 +5937,7 @@ async function readMobileRelayBaseUrl(env = process.env, projectRoot) {
5904
5937
  const envValue = typeof raw === "string" ? raw.trim() : "";
5905
5938
  if (envValue !== "") return envValue;
5906
5939
  if (projectRoot !== void 0) {
5907
- const { readRelayUrls } = await import("../relay-url-store-Dq3vpd95.js");
5940
+ const { readRelayUrls } = await import("../relay-url-store-DjKJJZ0d.js");
5908
5941
  const stored = await readRelayUrls({ projectRoot });
5909
5942
  if (stored?.relayBaseUrl !== void 0) return stored.relayBaseUrl;
5910
5943
  }
@@ -6957,7 +6990,7 @@ function createDevServer(deps = {}) {
6957
6990
  const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
6958
6991
  const server = new Server({
6959
6992
  name: "ait-devtools",
6960
- version: "0.1.71"
6993
+ version: "0.1.73"
6961
6994
  }, { capabilities: { tools: {} } });
6962
6995
  server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
6963
6996
  server.setRequestHandler(CallToolRequestSchema, async (request) => {