@ait-co/devtools 0.1.143 → 0.2.0

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 (223) hide show
  1. package/README.en.md +65 -163
  2. package/README.md +65 -192
  3. package/dist/in-app/auto.d.ts +1 -138
  4. package/dist/in-app/auto.js +28 -1102
  5. package/dist/in-app/auto.js.map +1 -1
  6. package/dist/in-app/index.d.ts +38 -547
  7. package/dist/in-app/index.d.ts.map +1 -1
  8. package/dist/in-app/index.js +62 -939
  9. package/dist/in-app/index.js.map +1 -1
  10. package/dist/mcp/cli.d.ts +1 -54
  11. package/dist/mcp/cli.js +33 -9720
  12. package/dist/mcp/cli.js.map +1 -1
  13. package/dist/mcp/server.d.ts +1 -88
  14. package/dist/mcp/server.js +36 -1076
  15. package/dist/mcp/server.js.map +1 -1
  16. package/dist/mock/index.d.ts +19 -20
  17. package/dist/mock/index.d.ts.map +1 -1
  18. package/dist/mock/index.js.map +1 -1
  19. package/dist/optional-peers-CDEFhlhJ.cjs +131 -0
  20. package/dist/optional-peers-CDEFhlhJ.cjs.map +1 -0
  21. package/dist/optional-peers-FdVbUJst.js +96 -0
  22. package/dist/optional-peers-FdVbUJst.js.map +1 -0
  23. package/dist/panel/index.js +1 -103
  24. package/dist/panel/index.js.map +1 -1
  25. package/dist/relay-url-store-CPZAn-T5.js +107 -0
  26. package/dist/relay-url-store-CPZAn-T5.js.map +1 -0
  27. package/dist/relay-url-store-DLjlvMSA.cjs +108 -0
  28. package/dist/relay-url-store-DLjlvMSA.cjs.map +1 -0
  29. package/dist/stubs/bin-devtools-mcp.js +58 -0
  30. package/dist/stubs/bin-devtools-mcp.js.map +1 -0
  31. package/dist/stubs/bin-devtools-test.d.ts +2 -0
  32. package/dist/stubs/bin-devtools-test.js +55 -0
  33. package/dist/stubs/bin-devtools-test.js.map +1 -0
  34. package/dist/test-runner/config.d.ts +1 -231
  35. package/dist/test-runner/config.js +41 -45
  36. package/dist/test-runner/config.js.map +1 -1
  37. package/dist/{tunnel-BVSMXctM.cjs → tunnel-BKZkOyQp.cjs} +27 -75
  38. package/dist/tunnel-BKZkOyQp.cjs.map +1 -0
  39. package/dist/{tunnel-D7xkimBu.js → tunnel-CqSCIrdU.js} +27 -75
  40. package/dist/tunnel-CqSCIrdU.js.map +1 -0
  41. package/dist/unplugin/index.cjs +17 -30
  42. package/dist/unplugin/index.cjs.map +1 -1
  43. package/dist/unplugin/index.d.cts +34 -5
  44. package/dist/unplugin/index.d.cts.map +1 -1
  45. package/dist/unplugin/index.d.ts +35 -6
  46. package/dist/unplugin/index.d.ts.map +1 -1
  47. package/dist/unplugin/index.js +17 -30
  48. package/dist/unplugin/index.js.map +1 -1
  49. package/dist/unplugin/tunnel.cjs +61 -74
  50. package/dist/unplugin/tunnel.cjs.map +1 -1
  51. package/dist/unplugin/tunnel.d.cts +22 -16
  52. package/dist/unplugin/tunnel.d.cts.map +1 -1
  53. package/dist/unplugin/tunnel.d.ts +22 -16
  54. package/dist/unplugin/tunnel.d.ts.map +1 -1
  55. package/dist/unplugin/tunnel.js +61 -74
  56. package/dist/unplugin/tunnel.js.map +1 -1
  57. package/package.json +17 -22
  58. package/dist/attach-orchestrator-0F0m_UqQ.js +0 -1845
  59. package/dist/attach-orchestrator-0F0m_UqQ.js.map +0 -1
  60. package/dist/attach-orchestrator-D65KxFy_.js +0 -1831
  61. package/dist/attach-orchestrator-D65KxFy_.js.map +0 -1
  62. package/dist/attach-orchestrator-DL3NQ9ca.js +0 -1846
  63. package/dist/attach-orchestrator-DL3NQ9ca.js.map +0 -1
  64. package/dist/bundle-C796JIwG.d.ts +0 -159
  65. package/dist/bundle-C796JIwG.d.ts.map +0 -1
  66. package/dist/capture-DsP525OZ.d.ts +0 -58
  67. package/dist/capture-DsP525OZ.d.ts.map +0 -1
  68. package/dist/cdp-connection-rP1WdnH5.d.ts +0 -287
  69. package/dist/cdp-connection-rP1WdnH5.d.ts.map +0 -1
  70. package/dist/cell-BaLvusOl.js +0 -68
  71. package/dist/cell-BaLvusOl.js.map +0 -1
  72. package/dist/cell-CBUS3-nT.js +0 -274
  73. package/dist/cell-CBUS3-nT.js.map +0 -1
  74. package/dist/cell-EBKKpAAT.js +0 -307
  75. package/dist/cell-EBKKpAAT.js.map +0 -1
  76. package/dist/chii-relay-BZ3HqWL5.js +0 -304
  77. package/dist/chii-relay-BZ3HqWL5.js.map +0 -1
  78. package/dist/chii-relay-D7eK2acz.cjs +0 -304
  79. package/dist/chii-relay-D7eK2acz.cjs.map +0 -1
  80. package/dist/debug-server-B3ABDrRI.js +0 -456
  81. package/dist/debug-server-B3ABDrRI.js.map +0 -1
  82. package/dist/debug-server-BWhwrVXa.js +0 -1158
  83. package/dist/debug-server-BWhwrVXa.js.map +0 -1
  84. package/dist/debug-server-CfQNxxGW.js +0 -600
  85. package/dist/debug-server-CfQNxxGW.js.map +0 -1
  86. package/dist/deeplink-B5-Hxu0Q.js +0 -62
  87. package/dist/deeplink-B5-Hxu0Q.js.map +0 -1
  88. package/dist/deeplink-BpO9qc-D.js +0 -62
  89. package/dist/deeplink-BpO9qc-D.js.map +0 -1
  90. package/dist/deeplink-BzdbA1gV.cjs +0 -62
  91. package/dist/deeplink-BzdbA1gV.cjs.map +0 -1
  92. package/dist/deeplink-DCScMYcp.cjs +0 -62
  93. package/dist/deeplink-DCScMYcp.cjs.map +0 -1
  94. package/dist/devtools-opener-3Drge_RJ.js +0 -75
  95. package/dist/devtools-opener-3Drge_RJ.js.map +0 -1
  96. package/dist/devtools-opener-B8nxrxqu.js +0 -71
  97. package/dist/devtools-opener-B8nxrxqu.js.map +0 -1
  98. package/dist/devtools-opener-BDY0w3_0.cjs +0 -68
  99. package/dist/devtools-opener-BDY0w3_0.cjs.map +0 -1
  100. package/dist/devtools-opener-BTl5A6Cd.js +0 -71
  101. package/dist/devtools-opener-BTl5A6Cd.js.map +0 -1
  102. package/dist/devtools-opener-CJpEsXXQ.js +0 -76
  103. package/dist/devtools-opener-CJpEsXXQ.js.map +0 -1
  104. package/dist/devtools-opener-CxtryS8c.js +0 -75
  105. package/dist/devtools-opener-CxtryS8c.js.map +0 -1
  106. package/dist/devtools-opener-iv1OwfJN.cjs +0 -68
  107. package/dist/devtools-opener-iv1OwfJN.cjs.map +0 -1
  108. package/dist/in-app/auto.d.ts.map +0 -1
  109. package/dist/mcp/cli.d.ts.map +0 -1
  110. package/dist/mcp/server.d.ts.map +0 -1
  111. package/dist/pool-DcaaOwUq.d.ts +0 -14761
  112. package/dist/pool-DcaaOwUq.d.ts.map +0 -1
  113. package/dist/qr-http-server--gl2-WKc.cjs +0 -1644
  114. package/dist/qr-http-server--gl2-WKc.cjs.map +0 -1
  115. package/dist/qr-http-server-Bb7lMeqi.js +0 -1644
  116. package/dist/qr-http-server-Bb7lMeqi.js.map +0 -1
  117. package/dist/qr-http-server-C_lqOrgc.js +0 -1644
  118. package/dist/qr-http-server-C_lqOrgc.js.map +0 -1
  119. package/dist/qr-http-server-CopuMbub.js +0 -1644
  120. package/dist/qr-http-server-CopuMbub.js.map +0 -1
  121. package/dist/qr-http-server-D-Off6K1.cjs +0 -1644
  122. package/dist/qr-http-server-D-Off6K1.cjs.map +0 -1
  123. package/dist/qr-http-server-DrbIVDjO.js +0 -1645
  124. package/dist/qr-http-server-DrbIVDjO.js.map +0 -1
  125. package/dist/qr-http-server-n1twN18z.js +0 -1644
  126. package/dist/qr-http-server-n1twN18z.js.map +0 -1
  127. package/dist/relay-factory-N9QobQxG.js +0 -206
  128. package/dist/relay-factory-N9QobQxG.js.map +0 -1
  129. package/dist/relay-secret-store-BPhN1upr.js +0 -240
  130. package/dist/relay-secret-store-BPhN1upr.js.map +0 -1
  131. package/dist/relay-secret-store-Bmyleu0A.js +0 -154
  132. package/dist/relay-secret-store-Bmyleu0A.js.map +0 -1
  133. package/dist/relay-secret-store-CQenfcSL.js +0 -154
  134. package/dist/relay-secret-store-CQenfcSL.js.map +0 -1
  135. package/dist/relay-secret-store-DKxs7zwq.js +0 -153
  136. package/dist/relay-secret-store-DKxs7zwq.js.map +0 -1
  137. package/dist/relay-secret-store-DWKdV-eY.cjs +0 -241
  138. package/dist/relay-secret-store-DWKdV-eY.cjs.map +0 -1
  139. package/dist/relay-secret-store-WJ8EGkIl.js +0 -153
  140. package/dist/relay-secret-store-WJ8EGkIl.js.map +0 -1
  141. package/dist/relay-url-store-1FGuSYAn.cjs +0 -115
  142. package/dist/relay-url-store-1FGuSYAn.cjs.map +0 -1
  143. package/dist/relay-url-store-BR2XodiO.js +0 -123
  144. package/dist/relay-url-store-BR2XodiO.js.map +0 -1
  145. package/dist/relay-url-store-Bskcyeg8.js +0 -114
  146. package/dist/relay-url-store-Bskcyeg8.js.map +0 -1
  147. package/dist/relay-url-store-CH63fVCm.js +0 -122
  148. package/dist/relay-url-store-CH63fVCm.js.map +0 -1
  149. package/dist/relay-url-store-DaY1QPes.js +0 -123
  150. package/dist/relay-url-store-DaY1QPes.js.map +0 -1
  151. package/dist/relay-url-store-xmUuTjXA.js +0 -122
  152. package/dist/relay-url-store-xmUuTjXA.js.map +0 -1
  153. package/dist/relay-worker-B5HKkGUY.js +0 -832
  154. package/dist/relay-worker-B5HKkGUY.js.map +0 -1
  155. package/dist/relay-worker-YdlpZQl9.d.ts +0 -214
  156. package/dist/relay-worker-YdlpZQl9.d.ts.map +0 -1
  157. package/dist/rolldown-runtime-DGkTqVfb.js +0 -15
  158. package/dist/rolldown-runtime-DUslC3ob.js +0 -14
  159. package/dist/runtime-kn9DxOeg.d.ts +0 -249
  160. package/dist/runtime-kn9DxOeg.d.ts.map +0 -1
  161. package/dist/test-runner/bin.js +0 -2584
  162. package/dist/test-runner/bin.js.map +0 -1
  163. package/dist/test-runner/bridge-stub.d.ts +0 -125
  164. package/dist/test-runner/bridge-stub.d.ts.map +0 -1
  165. package/dist/test-runner/bridge-stub.js +0 -92
  166. package/dist/test-runner/bridge-stub.js.map +0 -1
  167. package/dist/test-runner/bundle.d.ts +0 -2
  168. package/dist/test-runner/bundle.js +0 -439
  169. package/dist/test-runner/bundle.js.map +0 -1
  170. package/dist/test-runner/capture.d.ts +0 -2
  171. package/dist/test-runner/capture.js +0 -44
  172. package/dist/test-runner/capture.js.map +0 -1
  173. package/dist/test-runner/config.d.ts.map +0 -1
  174. package/dist/test-runner/method-pace.d.ts +0 -82
  175. package/dist/test-runner/method-pace.d.ts.map +0 -1
  176. package/dist/test-runner/method-pace.js +0 -120
  177. package/dist/test-runner/method-pace.js.map +0 -1
  178. package/dist/test-runner/pool.d.ts +0 -2
  179. package/dist/test-runner/pool.js +0 -136
  180. package/dist/test-runner/pool.js.map +0 -1
  181. package/dist/test-runner/relay-factory.d.ts +0 -11245
  182. package/dist/test-runner/relay-factory.d.ts.map +0 -1
  183. package/dist/test-runner/relay-factory.js +0 -206
  184. package/dist/test-runner/relay-factory.js.map +0 -1
  185. package/dist/test-runner/relay-worker.d.ts +0 -2
  186. package/dist/test-runner/relay-worker.js +0 -2
  187. package/dist/test-runner/report.d.ts +0 -163
  188. package/dist/test-runner/report.d.ts.map +0 -1
  189. package/dist/test-runner/report.js +0 -198
  190. package/dist/test-runner/report.js.map +0 -1
  191. package/dist/test-runner/rpc.d.ts +0 -56
  192. package/dist/test-runner/rpc.d.ts.map +0 -1
  193. package/dist/test-runner/rpc.js +0 -98
  194. package/dist/test-runner/rpc.js.map +0 -1
  195. package/dist/test-runner/runtime.d.ts +0 -2
  196. package/dist/test-runner/runtime.js +0 -659
  197. package/dist/test-runner/runtime.js.map +0 -1
  198. package/dist/test-runner/task-graph.d.ts +0 -38
  199. package/dist/test-runner/task-graph.d.ts.map +0 -1
  200. package/dist/test-runner/task-graph.js +0 -182
  201. package/dist/test-runner/task-graph.js.map +0 -1
  202. package/dist/throttle-DKKzX1qC.js +0 -59
  203. package/dist/throttle-DKKzX1qC.js.map +0 -1
  204. package/dist/totp-95OAa20j.js +0 -64
  205. package/dist/totp-95OAa20j.js.map +0 -1
  206. package/dist/totp-BjtoQNfu.cjs +0 -64
  207. package/dist/totp-BjtoQNfu.cjs.map +0 -1
  208. package/dist/totp-CZLLKfOC.js +0 -200
  209. package/dist/totp-CZLLKfOC.js.map +0 -1
  210. package/dist/totp-DAxys-r0.js +0 -199
  211. package/dist/totp-DAxys-r0.js.map +0 -1
  212. package/dist/totp-DIbrZtI7.js +0 -189
  213. package/dist/totp-DIbrZtI7.js.map +0 -1
  214. package/dist/totp-Df252ZdA.cjs +0 -192
  215. package/dist/totp-Df252ZdA.cjs.map +0 -1
  216. package/dist/totp-DfekTBk3.js +0 -211
  217. package/dist/totp-DfekTBk3.js.map +0 -1
  218. package/dist/totp-Dwft0Kz7.js +0 -3
  219. package/dist/totp-WY6l0ysP.js +0 -190
  220. package/dist/totp-WY6l0ysP.js.map +0 -1
  221. package/dist/tunnel-BVSMXctM.cjs.map +0 -1
  222. package/dist/tunnel-D7xkimBu.js.map +0 -1
  223. /package/dist/{test-runner/bin.d.ts → stubs/bin-devtools-mcp.d.ts} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"relay-url-store-DaY1QPes.js","names":[],"sources":["../src/mcp/relay-url-store.ts"],"sourcesContent":["/**\n * Project-local ephemeral URL store (#424).\n *\n * Environment-2 (\"AITC Sandbox PWA\") cold-start requires two ephemeral URLs:\n * - `relayBaseUrl` — the CDP relay's https base (was `AIT_RELAY_BASE_URL`)\n * - `tunnelBaseUrl` — the app's https tunnel base (was `AIT_TUNNEL_BASE_URL`)\n *\n * Quick-tunnel URLs change every run. Previously the user had to copy-paste them\n * into env vars on every cold-start — a single typo silently broke attach. This\n * module replaces that manual hand-off with a file-based discovery pattern that\n * exactly mirrors the `.ait_relay` TOTP-secret store (relay-secret-store.ts).\n *\n * Two surfaces, intentionally split by who is allowed to write:\n *\n * - {@link writeRelayUrls} — WRITE path, called ONLY from the unplugin\n * (env-2 tunnel boot). Writes JSON to `<projectRoot>/.ait_urls` (0600) on\n * every boot (`flag: 'w'` — overwrite). A single file — no directory is\n * created.\n *\n * - {@link readRelayUrls} — READ-ONLY path, called from the MCP daemon as a\n * fallback when `AIT_RELAY_BASE_URL`/`AIT_TUNNEL_BASE_URL` are not set. It\n * NEVER writes, chmods, or creates anything: it only reads an existing\n * `.ait_urls`. On any failure (missing file / bad JSON / wrong shape) it\n * returns `null` silently, letting the downstream assertion be the single\n * fail-fast.\n *\n * - {@link deleteRelayUrls} — called from the unplugin `cleanup()` on\n * teardown (via `void deleteRelayUrls(...)`). A stale `.ait_urls` pointing\n * at a dead tunnel would cause the MCP daemon to attempt a doomed attach on\n * the next cold-start — deletion is non-negotiable. Silently swallows all\n * errors.\n *\n * Design note: the env vars (`AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL`) are\n * PRESERVED as operator overrides. The file is the fallback — env wins.\n *\n * Why `nearestPackageJsonDir` instead of `process.cwd()`: see relay-secret-store.ts.\n * The unplugin (writer) and the MCP daemon (reader) both anchor to the nearest\n * package.json from their respective `projectRoot` inputs, ensuring both sides\n * find the same file.\n *\n * SECRET-HANDLING: `relayBaseUrl` and `tunnelBaseUrl` carry the relay/tunnel host\n * — same sensitivity class as `.ait_relay`. The raw URL values, partial values,\n * and the resolved file path MUST NOT appear in any log, error message, stdout,\n * stderr, or assertion output anywhere in this module or at its call sites.\n * Only boolean pass/fail signals are safe to surface. The file is written mode\n * 0600.\n */\n\nimport { join } from 'node:path';\nimport { nearestPackageJsonDir } from './relay-secret-store.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Project-local ephemeral URL file name (single file, not a directory). */\nexport const URLS_FILE_NAME = '.ait_urls';\n\n// ---------------------------------------------------------------------------\n// Dependency injection surfaces\n// ---------------------------------------------------------------------------\n\n/** Minimal fs subset needed by {@link writeRelayUrls} — injectable for tests. */\nexport interface RelayUrlWriteFs {\n writeFileSync(path: string, data: string, options: { mode: number; flag: string }): void;\n existsSync(path: string): boolean;\n}\n\n/** Minimal fs subset needed by {@link readRelayUrls} — injectable for tests. */\nexport interface RelayUrlReadFs {\n existsSync(path: string): boolean;\n readFileSync(path: string, encoding: BufferEncoding): string;\n}\n\n/** Minimal fs subset needed by {@link deleteRelayUrls} — injectable for tests. */\nexport interface RelayUrlDeleteFs {\n existsSync(path: string): boolean;\n unlinkSync(path: string): void;\n}\n\n// ---------------------------------------------------------------------------\n// Path helper\n// ---------------------------------------------------------------------------\n\n/**\n * Absolute path to the project-local `.ait_urls` file for a given start\n * directory (resolved against the nearest package.json directory).\n *\n * Exported so tests can compute the expected path without duplicating the\n * resolution logic.\n */\nexport function urlsFilePath(start: string, existsSyncFn: (path: string) => boolean): string {\n return join(nearestPackageJsonDir(start, existsSyncFn), URLS_FILE_NAME);\n}\n\n// ---------------------------------------------------------------------------\n// WRITE path (unplugin only) — overwrite on every boot\n// ---------------------------------------------------------------------------\n\nexport interface WriteRelayUrlsDeps {\n /** Project root (typically Vite `server.config.root`). */\n projectRoot: string;\n /**\n * The CDP relay's https base URL (same value as `AIT_RELAY_BASE_URL`).\n * Omit when the relay was not started (e.g. `cdp:false`).\n * SECRET-HANDLING: never log this value.\n */\n relayBaseUrl?: string;\n /**\n * The CDP relay's LOCAL http base URL (`http://127.0.0.1:<relay-port>`).\n * Set when `cdp: true` and the local relay port is known. Used by the MCP\n * daemon's `bootExternalRelayFamily` to build the Chii inspector URL against\n * the local relay rather than the cloudflare tunnel, so front_end page load\n * and the client WS leg do not traverse the tunnel (issue #530).\n * SECRET-HANDLING: local loopback URL — no tunnel host, safe to surface.\n */\n relayLocalUrl?: string;\n /**\n * The app tunnel's https base URL (same value as `AIT_TUNNEL_BASE_URL`).\n * Omit when no tunnel URL is available.\n * SECRET-HANDLING: never log this value.\n */\n tunnelBaseUrl?: string;\n /** Filesystem operations. Defaults to node:fs synchronous functions. */\n fs?: RelayUrlWriteFs;\n /** existsSync used to resolve the nearest package.json directory. Defaults to node:fs. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Writes `{ relayBaseUrl, tunnelBaseUrl }` (omitting absent keys) to\n * `<projectRoot>/.ait_urls` (mode 0600). Uses `flag: 'w'` (overwrite) because\n * URLs are ephemeral — a fresh URL replaces the previous one on every boot.\n *\n * Unlike the `.ait_relay` secret store this does NOT use `O_EXCL` (`'wx'`):\n * there is no race concern here (only the unplugin writes this file) and the\n * URL must be fresh on every cold-start.\n *\n * Called ONLY from the unplugin (env-2 tunnel boot). The MCP daemon uses\n * {@link readRelayUrls} (read-only) — it must never write.\n *\n * SECRET-HANDLING: URL values are never logged.\n */\nexport async function writeRelayUrls(deps: WriteRelayUrlsDeps): Promise<void> {\n const { projectRoot, relayBaseUrl, tunnelBaseUrl, fs: fsDep, existsSync: existsSyncDep } = deps;\n\n const fs: RelayUrlWriteFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n // Build the payload — omit keys whose values are absent.\n const payload: { relayBaseUrl?: string; relayLocalUrl?: string; tunnelBaseUrl?: string } = {};\n if (typeof relayBaseUrl === 'string' && relayBaseUrl !== '') {\n payload.relayBaseUrl = relayBaseUrl;\n }\n const { relayLocalUrl } = deps;\n if (typeof relayLocalUrl === 'string' && relayLocalUrl !== '') {\n payload.relayLocalUrl = relayLocalUrl;\n }\n if (typeof tunnelBaseUrl === 'string' && tunnelBaseUrl !== '') {\n payload.tunnelBaseUrl = tunnelBaseUrl;\n }\n\n // SECRET-HANDLING: JSON content (which includes URL values) is written to\n // the file only — never to any log, stdout, or stderr.\n const data = JSON.stringify(payload);\n\n // Overwrite on every boot (`flag: 'w'`) — URLs are ephemeral.\n fs.writeFileSync(filePath, data, { mode: 0o600, flag: 'w' });\n}\n\n// ---------------------------------------------------------------------------\n// READ-ONLY path (daemon only) — never writes, chmods, or creates anything\n// ---------------------------------------------------------------------------\n\nexport interface ReadRelayUrlsDeps {\n /** Project root supplied per-debug-session. When omitted, returns `null`. */\n projectRoot?: string;\n /** Read-only filesystem operations. Defaults to node:fs (existsSync + readFileSync). */\n fs?: RelayUrlReadFs;\n /** existsSync used to resolve the nearest package.json directory. Defaults to node:fs. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Reads `<projectRoot>/.ait_urls` and returns the stored URLs, or `null` on\n * any failure.\n *\n * Strictly READ-ONLY: only `existsSync` + `readFileSync`. Never writes,\n * chmods, or creates files/directories.\n *\n * Returns `null` (silently, no throw, no log) on:\n * - missing `projectRoot`\n * - missing `.ait_urls` file\n * - unreadable file (permissions, transient FS error)\n * - invalid JSON\n * - wrong shape (non-object, non-string values)\n *\n * Trims string values before returning. Ignores non-string fields.\n *\n * SECRET-HANDLING: URL values and the file path are never logged.\n */\nexport async function readRelayUrls(deps?: ReadRelayUrlsDeps): Promise<{\n relayBaseUrl?: string;\n relayLocalUrl?: string;\n tunnelBaseUrl?: string;\n} | null> {\n const { projectRoot, fs: fsDep, existsSync: existsSyncDep } = deps ?? {};\n\n if (projectRoot === undefined) {\n return null;\n }\n\n const fs: RelayUrlReadFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n // Unreadable file — silent no-op, let the downstream assert be the fail-fast.\n // SECRET-HANDLING: the error and path are not surfaced.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // Invalid JSON — silent no-op.\n return null;\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const result: { relayBaseUrl?: string; relayLocalUrl?: string; tunnelBaseUrl?: string } = {};\n\n const relay = obj.relayBaseUrl;\n if (typeof relay === 'string') {\n const trimmed = relay.trim();\n if (trimmed !== '') result.relayBaseUrl = trimmed;\n }\n\n const relayLocal = obj.relayLocalUrl;\n if (typeof relayLocal === 'string') {\n const trimmed = relayLocal.trim();\n if (trimmed !== '') result.relayLocalUrl = trimmed;\n }\n\n const tunnel = obj.tunnelBaseUrl;\n if (typeof tunnel === 'string') {\n const trimmed = tunnel.trim();\n if (trimmed !== '') result.tunnelBaseUrl = trimmed;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// DELETE path (unplugin cleanup only)\n// ---------------------------------------------------------------------------\n\nexport interface DeleteRelayUrlsDeps {\n /** Project root. */\n projectRoot: string;\n /** Filesystem operations. Defaults to node:fs (existsSync + unlinkSync). */\n fs?: RelayUrlDeleteFs;\n /** existsSync used to resolve the nearest package.json directory. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Removes `<projectRoot>/.ait_urls` if present. Silently swallows ENOENT and\n * any other error so cleanup always succeeds.\n *\n * Called ONLY from the unplugin's `cleanup()` on `httpServer 'close'` + signals\n * (via `void deleteRelayUrls(...)`). A stale `.ait_urls` pointing at a dead\n * tunnel would cause the MCP daemon to attempt a doomed attach on the next\n * cold-start — deletion is non-negotiable.\n *\n * SECRET-HANDLING: the file path is never logged.\n */\nexport async function deleteRelayUrls(deps: DeleteRelayUrlsDeps): Promise<void> {\n const { projectRoot, fs: fsDep, existsSync: existsSyncDep } = deps;\n\n const fs: RelayUrlDeleteFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n try {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Swallow ENOENT and any other error — cleanup is best-effort.\n // SECRET-HANDLING: the path is not logged.\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,iBAAiB;;;;;;;;AAmC9B,SAAgB,aAAa,OAAe,cAAiD;AAC3F,QAAO,KAAK,sBAAsB,OAAO,aAAa,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;AA+GzE,eAAsB,cAAc,MAI1B;CACR,MAAM,EAAE,aAAa,IAAI,OAAO,YAAY,kBAAkB,QAAQ,EAAE;AAExE,KAAI,gBAAgB,KAAA,EAClB,QAAO;CAGT,MAAM,KAAqB,SAAU,MAAM,OAAO;CAGlD,MAAM,WAAW,aAAa,aAFkB,iBAAiB,GAAG,WAEZ;AAExD,KAAI,CAAC,GAAG,WAAW,SAAS,CAC1B,QAAO;CAGT,IAAI;AACJ,KAAI;AACF,QAAM,GAAG,aAAa,UAAU,OAAO;SACjC;AAGN,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AAEN,SAAO;;AAGT,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACxE,QAAO;CAGT,MAAM,MAAM;CACZ,MAAM,SAAoF,EAAE;CAE5F,MAAM,QAAQ,IAAI;AAClB,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,YAAY,GAAI,QAAO,eAAe;;CAG5C,MAAM,aAAa,IAAI;AACvB,KAAI,OAAO,eAAe,UAAU;EAClC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,YAAY,GAAI,QAAO,gBAAgB;;CAG7C,MAAM,SAAS,IAAI;AACnB,KAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,YAAY,GAAI,QAAO,gBAAgB;;AAG7C,QAAO"}
@@ -1,122 +0,0 @@
1
- import { nearestPackageJsonDir } from "./relay-secret-store-DKxs7zwq.js";
2
- import { join } from "node:path";
3
- //#region src/mcp/relay-url-store.ts
4
- /**
5
- * Project-local ephemeral URL store (#424).
6
- *
7
- * Environment-2 ("AITC Sandbox PWA") cold-start requires two ephemeral URLs:
8
- * - `relayBaseUrl` — the CDP relay's https base (was `AIT_RELAY_BASE_URL`)
9
- * - `tunnelBaseUrl` — the app's https tunnel base (was `AIT_TUNNEL_BASE_URL`)
10
- *
11
- * Quick-tunnel URLs change every run. Previously the user had to copy-paste them
12
- * into env vars on every cold-start — a single typo silently broke attach. This
13
- * module replaces that manual hand-off with a file-based discovery pattern that
14
- * exactly mirrors the `.ait_relay` TOTP-secret store (relay-secret-store.ts).
15
- *
16
- * Two surfaces, intentionally split by who is allowed to write:
17
- *
18
- * - {@link writeRelayUrls} — WRITE path, called ONLY from the unplugin
19
- * (env-2 tunnel boot). Writes JSON to `<projectRoot>/.ait_urls` (0600) on
20
- * every boot (`flag: 'w'` — overwrite). A single file — no directory is
21
- * created.
22
- *
23
- * - {@link readRelayUrls} — READ-ONLY path, called from the MCP daemon as a
24
- * fallback when `AIT_RELAY_BASE_URL`/`AIT_TUNNEL_BASE_URL` are not set. It
25
- * NEVER writes, chmods, or creates anything: it only reads an existing
26
- * `.ait_urls`. On any failure (missing file / bad JSON / wrong shape) it
27
- * returns `null` silently, letting the downstream assertion be the single
28
- * fail-fast.
29
- *
30
- * - {@link deleteRelayUrls} — called from the unplugin `cleanup()` on
31
- * teardown (via `void deleteRelayUrls(...)`). A stale `.ait_urls` pointing
32
- * at a dead tunnel would cause the MCP daemon to attempt a doomed attach on
33
- * the next cold-start — deletion is non-negotiable. Silently swallows all
34
- * errors.
35
- *
36
- * Design note: the env vars (`AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL`) are
37
- * PRESERVED as operator overrides. The file is the fallback — env wins.
38
- *
39
- * Why `nearestPackageJsonDir` instead of `process.cwd()`: see relay-secret-store.ts.
40
- * The unplugin (writer) and the MCP daemon (reader) both anchor to the nearest
41
- * package.json from their respective `projectRoot` inputs, ensuring both sides
42
- * find the same file.
43
- *
44
- * SECRET-HANDLING: `relayBaseUrl` and `tunnelBaseUrl` carry the relay/tunnel host
45
- * — same sensitivity class as `.ait_relay`. The raw URL values, partial values,
46
- * and the resolved file path MUST NOT appear in any log, error message, stdout,
47
- * stderr, or assertion output anywhere in this module or at its call sites.
48
- * Only boolean pass/fail signals are safe to surface. The file is written mode
49
- * 0600.
50
- */
51
- /** Project-local ephemeral URL file name (single file, not a directory). */
52
- const URLS_FILE_NAME = ".ait_urls";
53
- /**
54
- * Absolute path to the project-local `.ait_urls` file for a given start
55
- * directory (resolved against the nearest package.json directory).
56
- *
57
- * Exported so tests can compute the expected path without duplicating the
58
- * resolution logic.
59
- */
60
- function urlsFilePath(start, existsSyncFn) {
61
- return join(nearestPackageJsonDir(start, existsSyncFn), URLS_FILE_NAME);
62
- }
63
- /**
64
- * Reads `<projectRoot>/.ait_urls` and returns the stored URLs, or `null` on
65
- * any failure.
66
- *
67
- * Strictly READ-ONLY: only `existsSync` + `readFileSync`. Never writes,
68
- * chmods, or creates files/directories.
69
- *
70
- * Returns `null` (silently, no throw, no log) on:
71
- * - missing `projectRoot`
72
- * - missing `.ait_urls` file
73
- * - unreadable file (permissions, transient FS error)
74
- * - invalid JSON
75
- * - wrong shape (non-object, non-string values)
76
- *
77
- * Trims string values before returning. Ignores non-string fields.
78
- *
79
- * SECRET-HANDLING: URL values and the file path are never logged.
80
- */
81
- async function readRelayUrls(deps) {
82
- const { projectRoot, fs: fsDep, existsSync: existsSyncDep } = deps ?? {};
83
- if (projectRoot === void 0) return null;
84
- const fs = fsDep ?? await import("node:fs");
85
- const filePath = urlsFilePath(projectRoot, existsSyncDep ?? fs.existsSync);
86
- if (!fs.existsSync(filePath)) return null;
87
- let raw;
88
- try {
89
- raw = fs.readFileSync(filePath, "utf8");
90
- } catch {
91
- return null;
92
- }
93
- let parsed;
94
- try {
95
- parsed = JSON.parse(raw);
96
- } catch {
97
- return null;
98
- }
99
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
100
- const obj = parsed;
101
- const result = {};
102
- const relay = obj.relayBaseUrl;
103
- if (typeof relay === "string") {
104
- const trimmed = relay.trim();
105
- if (trimmed !== "") result.relayBaseUrl = trimmed;
106
- }
107
- const relayLocal = obj.relayLocalUrl;
108
- if (typeof relayLocal === "string") {
109
- const trimmed = relayLocal.trim();
110
- if (trimmed !== "") result.relayLocalUrl = trimmed;
111
- }
112
- const tunnel = obj.tunnelBaseUrl;
113
- if (typeof tunnel === "string") {
114
- const trimmed = tunnel.trim();
115
- if (trimmed !== "") result.tunnelBaseUrl = trimmed;
116
- }
117
- return result;
118
- }
119
- //#endregion
120
- export { readRelayUrls };
121
-
122
- //# sourceMappingURL=relay-url-store-xmUuTjXA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"relay-url-store-xmUuTjXA.js","names":[],"sources":["../src/mcp/relay-url-store.ts"],"sourcesContent":["/**\n * Project-local ephemeral URL store (#424).\n *\n * Environment-2 (\"AITC Sandbox PWA\") cold-start requires two ephemeral URLs:\n * - `relayBaseUrl` — the CDP relay's https base (was `AIT_RELAY_BASE_URL`)\n * - `tunnelBaseUrl` — the app's https tunnel base (was `AIT_TUNNEL_BASE_URL`)\n *\n * Quick-tunnel URLs change every run. Previously the user had to copy-paste them\n * into env vars on every cold-start — a single typo silently broke attach. This\n * module replaces that manual hand-off with a file-based discovery pattern that\n * exactly mirrors the `.ait_relay` TOTP-secret store (relay-secret-store.ts).\n *\n * Two surfaces, intentionally split by who is allowed to write:\n *\n * - {@link writeRelayUrls} — WRITE path, called ONLY from the unplugin\n * (env-2 tunnel boot). Writes JSON to `<projectRoot>/.ait_urls` (0600) on\n * every boot (`flag: 'w'` — overwrite). A single file — no directory is\n * created.\n *\n * - {@link readRelayUrls} — READ-ONLY path, called from the MCP daemon as a\n * fallback when `AIT_RELAY_BASE_URL`/`AIT_TUNNEL_BASE_URL` are not set. It\n * NEVER writes, chmods, or creates anything: it only reads an existing\n * `.ait_urls`. On any failure (missing file / bad JSON / wrong shape) it\n * returns `null` silently, letting the downstream assertion be the single\n * fail-fast.\n *\n * - {@link deleteRelayUrls} — called from the unplugin `cleanup()` on\n * teardown (via `void deleteRelayUrls(...)`). A stale `.ait_urls` pointing\n * at a dead tunnel would cause the MCP daemon to attempt a doomed attach on\n * the next cold-start — deletion is non-negotiable. Silently swallows all\n * errors.\n *\n * Design note: the env vars (`AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL`) are\n * PRESERVED as operator overrides. The file is the fallback — env wins.\n *\n * Why `nearestPackageJsonDir` instead of `process.cwd()`: see relay-secret-store.ts.\n * The unplugin (writer) and the MCP daemon (reader) both anchor to the nearest\n * package.json from their respective `projectRoot` inputs, ensuring both sides\n * find the same file.\n *\n * SECRET-HANDLING: `relayBaseUrl` and `tunnelBaseUrl` carry the relay/tunnel host\n * — same sensitivity class as `.ait_relay`. The raw URL values, partial values,\n * and the resolved file path MUST NOT appear in any log, error message, stdout,\n * stderr, or assertion output anywhere in this module or at its call sites.\n * Only boolean pass/fail signals are safe to surface. The file is written mode\n * 0600.\n */\n\nimport { join } from 'node:path';\nimport { nearestPackageJsonDir } from './relay-secret-store.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Project-local ephemeral URL file name (single file, not a directory). */\nexport const URLS_FILE_NAME = '.ait_urls';\n\n// ---------------------------------------------------------------------------\n// Dependency injection surfaces\n// ---------------------------------------------------------------------------\n\n/** Minimal fs subset needed by {@link writeRelayUrls} — injectable for tests. */\nexport interface RelayUrlWriteFs {\n writeFileSync(path: string, data: string, options: { mode: number; flag: string }): void;\n existsSync(path: string): boolean;\n}\n\n/** Minimal fs subset needed by {@link readRelayUrls} — injectable for tests. */\nexport interface RelayUrlReadFs {\n existsSync(path: string): boolean;\n readFileSync(path: string, encoding: BufferEncoding): string;\n}\n\n/** Minimal fs subset needed by {@link deleteRelayUrls} — injectable for tests. */\nexport interface RelayUrlDeleteFs {\n existsSync(path: string): boolean;\n unlinkSync(path: string): void;\n}\n\n// ---------------------------------------------------------------------------\n// Path helper\n// ---------------------------------------------------------------------------\n\n/**\n * Absolute path to the project-local `.ait_urls` file for a given start\n * directory (resolved against the nearest package.json directory).\n *\n * Exported so tests can compute the expected path without duplicating the\n * resolution logic.\n */\nexport function urlsFilePath(start: string, existsSyncFn: (path: string) => boolean): string {\n return join(nearestPackageJsonDir(start, existsSyncFn), URLS_FILE_NAME);\n}\n\n// ---------------------------------------------------------------------------\n// WRITE path (unplugin only) — overwrite on every boot\n// ---------------------------------------------------------------------------\n\nexport interface WriteRelayUrlsDeps {\n /** Project root (typically Vite `server.config.root`). */\n projectRoot: string;\n /**\n * The CDP relay's https base URL (same value as `AIT_RELAY_BASE_URL`).\n * Omit when the relay was not started (e.g. `cdp:false`).\n * SECRET-HANDLING: never log this value.\n */\n relayBaseUrl?: string;\n /**\n * The CDP relay's LOCAL http base URL (`http://127.0.0.1:<relay-port>`).\n * Set when `cdp: true` and the local relay port is known. Used by the MCP\n * daemon's `bootExternalRelayFamily` to build the Chii inspector URL against\n * the local relay rather than the cloudflare tunnel, so front_end page load\n * and the client WS leg do not traverse the tunnel (issue #530).\n * SECRET-HANDLING: local loopback URL — no tunnel host, safe to surface.\n */\n relayLocalUrl?: string;\n /**\n * The app tunnel's https base URL (same value as `AIT_TUNNEL_BASE_URL`).\n * Omit when no tunnel URL is available.\n * SECRET-HANDLING: never log this value.\n */\n tunnelBaseUrl?: string;\n /** Filesystem operations. Defaults to node:fs synchronous functions. */\n fs?: RelayUrlWriteFs;\n /** existsSync used to resolve the nearest package.json directory. Defaults to node:fs. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Writes `{ relayBaseUrl, tunnelBaseUrl }` (omitting absent keys) to\n * `<projectRoot>/.ait_urls` (mode 0600). Uses `flag: 'w'` (overwrite) because\n * URLs are ephemeral — a fresh URL replaces the previous one on every boot.\n *\n * Unlike the `.ait_relay` secret store this does NOT use `O_EXCL` (`'wx'`):\n * there is no race concern here (only the unplugin writes this file) and the\n * URL must be fresh on every cold-start.\n *\n * Called ONLY from the unplugin (env-2 tunnel boot). The MCP daemon uses\n * {@link readRelayUrls} (read-only) — it must never write.\n *\n * SECRET-HANDLING: URL values are never logged.\n */\nexport async function writeRelayUrls(deps: WriteRelayUrlsDeps): Promise<void> {\n const { projectRoot, relayBaseUrl, tunnelBaseUrl, fs: fsDep, existsSync: existsSyncDep } = deps;\n\n const fs: RelayUrlWriteFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n // Build the payload — omit keys whose values are absent.\n const payload: { relayBaseUrl?: string; relayLocalUrl?: string; tunnelBaseUrl?: string } = {};\n if (typeof relayBaseUrl === 'string' && relayBaseUrl !== '') {\n payload.relayBaseUrl = relayBaseUrl;\n }\n const { relayLocalUrl } = deps;\n if (typeof relayLocalUrl === 'string' && relayLocalUrl !== '') {\n payload.relayLocalUrl = relayLocalUrl;\n }\n if (typeof tunnelBaseUrl === 'string' && tunnelBaseUrl !== '') {\n payload.tunnelBaseUrl = tunnelBaseUrl;\n }\n\n // SECRET-HANDLING: JSON content (which includes URL values) is written to\n // the file only — never to any log, stdout, or stderr.\n const data = JSON.stringify(payload);\n\n // Overwrite on every boot (`flag: 'w'`) — URLs are ephemeral.\n fs.writeFileSync(filePath, data, { mode: 0o600, flag: 'w' });\n}\n\n// ---------------------------------------------------------------------------\n// READ-ONLY path (daemon only) — never writes, chmods, or creates anything\n// ---------------------------------------------------------------------------\n\nexport interface ReadRelayUrlsDeps {\n /** Project root supplied per-debug-session. When omitted, returns `null`. */\n projectRoot?: string;\n /** Read-only filesystem operations. Defaults to node:fs (existsSync + readFileSync). */\n fs?: RelayUrlReadFs;\n /** existsSync used to resolve the nearest package.json directory. Defaults to node:fs. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Reads `<projectRoot>/.ait_urls` and returns the stored URLs, or `null` on\n * any failure.\n *\n * Strictly READ-ONLY: only `existsSync` + `readFileSync`. Never writes,\n * chmods, or creates files/directories.\n *\n * Returns `null` (silently, no throw, no log) on:\n * - missing `projectRoot`\n * - missing `.ait_urls` file\n * - unreadable file (permissions, transient FS error)\n * - invalid JSON\n * - wrong shape (non-object, non-string values)\n *\n * Trims string values before returning. Ignores non-string fields.\n *\n * SECRET-HANDLING: URL values and the file path are never logged.\n */\nexport async function readRelayUrls(deps?: ReadRelayUrlsDeps): Promise<{\n relayBaseUrl?: string;\n relayLocalUrl?: string;\n tunnelBaseUrl?: string;\n} | null> {\n const { projectRoot, fs: fsDep, existsSync: existsSyncDep } = deps ?? {};\n\n if (projectRoot === undefined) {\n return null;\n }\n\n const fs: RelayUrlReadFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n // Unreadable file — silent no-op, let the downstream assert be the fail-fast.\n // SECRET-HANDLING: the error and path are not surfaced.\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // Invalid JSON — silent no-op.\n return null;\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const result: { relayBaseUrl?: string; relayLocalUrl?: string; tunnelBaseUrl?: string } = {};\n\n const relay = obj.relayBaseUrl;\n if (typeof relay === 'string') {\n const trimmed = relay.trim();\n if (trimmed !== '') result.relayBaseUrl = trimmed;\n }\n\n const relayLocal = obj.relayLocalUrl;\n if (typeof relayLocal === 'string') {\n const trimmed = relayLocal.trim();\n if (trimmed !== '') result.relayLocalUrl = trimmed;\n }\n\n const tunnel = obj.tunnelBaseUrl;\n if (typeof tunnel === 'string') {\n const trimmed = tunnel.trim();\n if (trimmed !== '') result.tunnelBaseUrl = trimmed;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// DELETE path (unplugin cleanup only)\n// ---------------------------------------------------------------------------\n\nexport interface DeleteRelayUrlsDeps {\n /** Project root. */\n projectRoot: string;\n /** Filesystem operations. Defaults to node:fs (existsSync + unlinkSync). */\n fs?: RelayUrlDeleteFs;\n /** existsSync used to resolve the nearest package.json directory. */\n existsSync?: (path: string) => boolean;\n}\n\n/**\n * Removes `<projectRoot>/.ait_urls` if present. Silently swallows ENOENT and\n * any other error so cleanup always succeeds.\n *\n * Called ONLY from the unplugin's `cleanup()` on `httpServer 'close'` + signals\n * (via `void deleteRelayUrls(...)`). A stale `.ait_urls` pointing at a dead\n * tunnel would cause the MCP daemon to attempt a doomed attach on the next\n * cold-start — deletion is non-negotiable.\n *\n * SECRET-HANDLING: the file path is never logged.\n */\nexport async function deleteRelayUrls(deps: DeleteRelayUrlsDeps): Promise<void> {\n const { projectRoot, fs: fsDep, existsSync: existsSyncDep } = deps;\n\n const fs: RelayUrlDeleteFs = fsDep ?? (await import('node:fs'));\n const existsSyncFn: (path: string) => boolean = existsSyncDep ?? fs.existsSync;\n\n const filePath = urlsFilePath(projectRoot, existsSyncFn);\n\n try {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Swallow ENOENT and any other error — cleanup is best-effort.\n // SECRET-HANDLING: the path is not logged.\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,iBAAiB;;;;;;;;AAmC9B,SAAgB,aAAa,OAAe,cAAiD;AAC3F,QAAO,KAAK,sBAAsB,OAAO,aAAa,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;AA+GzE,eAAsB,cAAc,MAI1B;CACR,MAAM,EAAE,aAAa,IAAI,OAAO,YAAY,kBAAkB,QAAQ,EAAE;AAExE,KAAI,gBAAgB,KAAA,EAClB,QAAO;CAGT,MAAM,KAAqB,SAAU,MAAM,OAAO;CAGlD,MAAM,WAAW,aAAa,aAFkB,iBAAiB,GAAG,WAEZ;AAExD,KAAI,CAAC,GAAG,WAAW,SAAS,CAC1B,QAAO;CAGT,IAAI;AACJ,KAAI;AACF,QAAM,GAAG,aAAa,UAAU,OAAO;SACjC;AAGN,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AAEN,SAAO;;AAGT,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACxE,QAAO;CAGT,MAAM,MAAM;CACZ,MAAM,SAAoF,EAAE;CAE5F,MAAM,QAAQ,IAAI;AAClB,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,YAAY,GAAI,QAAO,eAAe;;CAG5C,MAAM,aAAa,IAAI;AACvB,KAAI,OAAO,eAAe,UAAU;EAClC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,YAAY,GAAI,QAAO,gBAAgB;;CAG7C,MAAM,SAAS,IAAI;AACnB,KAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,YAAY,GAAI,QAAO,gBAAgB;;AAG7C,QAAO"}