@ait-co/devtools 0.1.109 → 0.1.110

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 (56) hide show
  1. package/README.en.md +13 -31
  2. package/README.md +13 -31
  3. package/dist/in-app/auto.d.ts.map +1 -1
  4. package/dist/in-app/auto.js +40 -3
  5. package/dist/in-app/auto.js.map +1 -1
  6. package/dist/in-app/index.d.ts.map +1 -1
  7. package/dist/in-app/index.js +39 -2
  8. package/dist/in-app/index.js.map +1 -1
  9. package/dist/mcp/cli.d.ts +4 -16
  10. package/dist/mcp/cli.d.ts.map +1 -1
  11. package/dist/mcp/cli.js +623 -678
  12. package/dist/mcp/cli.js.map +1 -1
  13. package/dist/mcp/server.d.ts.map +1 -1
  14. package/dist/mcp/server.js +47 -59
  15. package/dist/mcp/server.js.map +1 -1
  16. package/dist/mock/index.d.ts.map +1 -1
  17. package/dist/mock/index.js +21 -2
  18. package/dist/mock/index.js.map +1 -1
  19. package/dist/panel/index.js +47 -32
  20. package/dist/panel/index.js.map +1 -1
  21. package/dist/{pool-CuVMzWGB.d.ts → pool-Bf6rQci4.d.ts} +206 -44
  22. package/dist/pool-Bf6rQci4.d.ts.map +1 -0
  23. package/dist/{qr-http-server-D4EAA7Il.js → qr-http-server-BJJt3ush.js} +8 -17
  24. package/dist/qr-http-server-BJJt3ush.js.map +1 -0
  25. package/dist/{qr-http-server-A9vld8r7.cjs → qr-http-server-BVS-HZjU.cjs} +8 -17
  26. package/dist/qr-http-server-BVS-HZjU.cjs.map +1 -0
  27. package/dist/{qr-http-server-Dj3Z0NHi.cjs → qr-http-server-C1T4RNbq.cjs} +8 -17
  28. package/dist/qr-http-server-C1T4RNbq.cjs.map +1 -0
  29. package/dist/{qr-http-server-HzdCLU8s.js → qr-http-server-Cs93vEPH.js} +8 -17
  30. package/dist/qr-http-server-Cs93vEPH.js.map +1 -0
  31. package/dist/test-runner/config.d.ts +1 -1
  32. package/dist/test-runner/pool.d.ts +1 -1
  33. package/dist/{tunnel-BjJROkcj.js → tunnel-Cpn3mA4u.js} +3 -3
  34. package/dist/tunnel-Cpn3mA4u.js.map +1 -0
  35. package/dist/{tunnel-d_G9AIFn.cjs → tunnel-Dj8Kf2QS.cjs} +3 -3
  36. package/dist/tunnel-Dj8Kf2QS.cjs.map +1 -0
  37. package/dist/unplugin/index.cjs +1 -1
  38. package/dist/unplugin/index.d.cts +196 -34
  39. package/dist/unplugin/index.d.cts.map +1 -1
  40. package/dist/unplugin/index.d.ts +196 -34
  41. package/dist/unplugin/index.d.ts.map +1 -1
  42. package/dist/unplugin/index.js +1 -1
  43. package/dist/unplugin/tunnel.cjs +2 -2
  44. package/dist/unplugin/tunnel.cjs.map +1 -1
  45. package/dist/unplugin/tunnel.d.cts +1 -1
  46. package/dist/unplugin/tunnel.d.ts +1 -1
  47. package/dist/unplugin/tunnel.js +2 -2
  48. package/dist/unplugin/tunnel.js.map +1 -1
  49. package/package.json +14 -14
  50. package/dist/pool-CuVMzWGB.d.ts.map +0 -1
  51. package/dist/qr-http-server-A9vld8r7.cjs.map +0 -1
  52. package/dist/qr-http-server-D4EAA7Il.js.map +0 -1
  53. package/dist/qr-http-server-Dj3Z0NHi.cjs.map +0 -1
  54. package/dist/qr-http-server-HzdCLU8s.js.map +0 -1
  55. package/dist/tunnel-BjJROkcj.js.map +0 -1
  56. package/dist/tunnel-d_G9AIFn.cjs.map +0 -1
package/README.en.md CHANGED
@@ -60,31 +60,16 @@ Load a dog-food bundle in the real Toss app WebView and debug it via the MCP rel
60
60
  ```bash
61
61
  devtools-mcp # start MCP server → QR printed in terminal
62
62
  # ait build && ait deploy --scheme-only
63
- # call build_attach_url scan QR Toss app loads bundle + relay attaches
63
+ # one start_attach(scheme_url) call generates the QR and waits for the phone to attach — scan it and the Toss app loads the bundle + relay attaches
64
64
  ```
65
65
 
66
66
  No HMR (Toss WebView cold-load only). Details: [`docs/scenarios/env-3.md`](./docs/scenarios/env-3.md)
67
67
 
68
68
  ---
69
69
 
70
- **Environment 4 — Live deployed app** (passed review, HMR off, read-only debug)
71
-
72
- Attach a relay to a live OPENED app to observe runtime behavior.
73
-
74
- ```bash
75
- devtools-mcp # start MCP server
76
- # In Claude Code: start_debug({mode: 'relay-live', confirm: true}) ← arms LIVE guard
77
- # call build_attach_url → scan QR → live app loads + relay attaches
78
- # call_sdk / evaluate: confirm: true required (LIVE guard — real users affected)
79
- ```
80
-
81
- `start_debug({mode: 'relay-live', confirm: true})` arms the LIVE guard in-session. Details: [`docs/scenarios/env-4.md`](./docs/scenarios/env-4.md)
82
-
83
- ---
84
-
85
70
  ## On-device debugging in one line
86
71
 
87
- To enable on-device CDP debugging in environments 2, 3, and 4, add **one line** to your mini-app entry (`main.tsx` or equivalent):
72
+ To enable on-device CDP debugging in environments 2 and 3, add **one line** to your mini-app entry (`main.tsx` or equivalent):
88
73
 
89
74
  ```ts
90
75
  // main.tsx (or the top of your mini-app entry)
@@ -106,11 +91,11 @@ For environments 3 and 4 (intoss-private relay), the relay QR deep-link carries
106
91
 
107
92
  **"QR window doesn't open"**
108
93
 
109
- Either `build_attach_url` wasn't called first, or the MCP server is running in a headless environment where no browser can be opened. The tool result always includes a text QR — scan it directly with your phone camera. On a local GUI machine, the dashboard opens automatically in the browser.
94
+ Either `start_attach` wasn't called first, or the MCP server is running in a headless environment where no browser can be opened. The tool result always includes a text QR — scan it directly with your phone camera. On a local GUI machine, the dashboard opens automatically in the browser.
110
95
 
111
96
  **"Page not attached" — list_pages returns an empty array**
112
97
 
113
- No page has joined the relay yet. Re-enter via `build_attach_url` → QR scan on your phone. When the MCP error message reads "page not attached — run build_attach_url then scan QR", this is the case.
98
+ No page has joined the relay yet. Re-enter via `start_attach` → QR scan on your phone. When the MCP error message reads "page not attached — run start_attach then scan QR", this is the case.
114
99
 
115
100
  **"Tunnel down" — no response or timeout**
116
101
 
@@ -118,7 +103,7 @@ A cloudflared quick tunnel can drop after a few hours. Restart the `devtools-mcp
118
103
 
119
104
  **"Page crash" — list_pages shows a non-null crashDetectedAt**
120
105
 
121
- The page on the phone died (OOM, JS exception, or native bridge crash). Relaunch the app, then re-attach via `build_attach_url` → QR scan. (Related: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
106
+ The page on the phone died (OOM, JS exception, or native bridge crash). Relaunch the app, then re-attach via `start_attach` → QR scan. (Related: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
122
107
 
123
108
  **"SDK not available" — window.__sdkCall not injected**
124
109
 
@@ -126,7 +111,7 @@ When `call_sdk` returns `ok: false, error: "window.__sdkCall is not available"`,
126
111
 
127
112
  **"QR scanned but auth rejected" — TOTP code expired**
128
113
 
129
- When `AIT_DEBUG_TOTP_SECRET` is set, `build_attach_url` automatically splices the current one-time TOTP code (`at=`) into the returned `attachUrl`. Each code covers a 30-second step, and the relay accepts ±6 steps (~3 min) of backwards skew. Scanning more than ~3 minutes after `totp.expiresAt` causes the relay to reject the request. Fix: call `build_attach_url` again to get a fresh URL and QR.
114
+ When `AIT_DEBUG_TOTP_SECRET` is set, `start_attach` automatically splices the current one-time TOTP code (`at=`) into the returned `attachUrl`. Each code covers a 30-second step, and the relay accepts ±6 steps (~3 min) of backwards skew. While waiting for the phone to attach, `start_attach` re-mints the code in-call as it nears its expiry window (the re-mint count is surfaced as `totp.reminted`), so you usually do not need to re-call during the wait. If you do scan an expired QR and the relay rejects it, call `start_attach` again to get a fresh URL and QR.
130
115
 
131
116
  ---
132
117
 
@@ -979,15 +964,14 @@ A local browser (env 1) and a phone Toss WebView (env 2/3) both speak CDP, so ev
979
964
  |---|---|---|---|---|
980
965
  | `--target=mobile` (env 2) | `devtools-mcp` → `start_debug({mode:'relay-sandbox'})` | `AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL` | Real-device Safari/WebKit PWA (external Chii relay + cloudflared tunnel, env 2) | console/network/page + DOM/snapshot/screenshot |
981
966
  | `--mode=debug --target=relay` (default, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | Dog-food bundle on a phone (CDP/Chii relay + cloudflared tunnel, env 3) | same + `AIT.*` |
982
- | `--mode=debug --target=relay` LIVE (env 4) | `devtools-mcp` → `start_debug({mode: 'relay-live', confirm: true})` | — (env 4 LIVE guard) | Live deployed app (env 4) — `call_sdk`/`evaluate` require `confirm: true` | same |
983
967
  | `--mode=debug --target=local` (env 1) | `devtools-mcp --target=local` | `MCP_ENV=mock` (auto) | Local Chromium launched by the MCP server (CDP direct-attach, no relay needed, env 1) | same |
984
968
  | `--mode=dev` | `devtools-mcp --mode=dev` | `MCP_ENV=mock` (auto) | Mock state from a running Vite dev server (AIT.* only, no CDP) | `AIT.*` (+ `devtools_get_mock_state` alias) |
985
969
 
986
- `--target=local` opens `AIT_DEVTOOLS_URL` (default `http://localhost:5173`) and attaches directly to a local Chromium — no relay or tunnel required. `--mode=dev` reads the mock-state HTTP endpoint of the Vite dev server and does not provide CDP tools. Switch environments in-session with `start_debug(mode)`: `relay-sandbox` (env 2 PWA), `relay-staging` (env 3 dogfood), `relay-live` (env 4, arms LIVE guard — `confirm: true` required), `local-browser` (env 1).
970
+ `--target=local` opens `AIT_DEVTOOLS_URL` (default `http://localhost:5173`) and attaches directly to a local Chromium — no relay or tunnel required. `--mode=dev` reads the mock-state HTTP endpoint of the Vite dev server and does not provide CDP tools. Switch environments in-session with `start_debug(mode)`: `relay-sandbox` (env 2 PWA), `relay-staging` (env 3 dogfood), `local-browser` (env 1).
987
971
 
988
972
  #### Environment 2 (real-device PWA CDP) — `--target=mobile`
989
973
 
990
- Debug on a real phone using Safari/WebKit without Toss review. The Vite dev server with [`tunnel:{cdp:true}`](#tunnel-option) brings up both an app HTTP tunnel and a Chii relay tunnel. The MCP server attaches to that relay and provides `build_attach_url` → launcher QR.
974
+ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev server with [`tunnel:{cdp:true}`](#tunnel-option) brings up both an app HTTP tunnel and a Chii relay tunnel. The MCP server attaches to that relay and provides `start_attach` → launcher QR.
991
975
 
992
976
  **Setup procedure:**
993
977
 
@@ -1018,7 +1002,7 @@ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev serv
1018
1002
  3. In a Claude Code session:
1019
1003
  ```
1020
1004
  start_debug({mode: 'relay-sandbox'})
1021
- build_attach_url()
1005
+ start_attach()
1022
1006
  ```
1023
1007
  Scan the QR with your phone camera. The launcher PWA opens the app in a frame and injects Chii target.js.
1024
1008
 
@@ -1032,7 +1016,7 @@ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev serv
1032
1016
 
1033
1017
  For a step-by-step walkthrough of the on-device relay debug loop (dog-food build → QR scan → relay attach) including common failure recovery, see **[`docs/dogfood-relay-loop.md`](./docs/dogfood-relay-loop.md)** (Korean). For crash triage — `list_pages.crashDetectedAt`, iOS Console.app `.ips` analysis, and the redact procedure — see **[`docs/crash-triage.md`](./docs/crash-triage.md)** (Korean).
1034
1018
 
1035
- Read-only tools only. Tools are registered in two tiers based on attach state — before attach, only the bootstrap tools (`build_attach_url`, `list_pages`) are visible; once a relay/local page attaches, the attach-dependent tools are registered dynamically in the same session via `notifications/tools/list_changed` (no session restart needed). The phone attach roundtrip is fully wired; all that remains is a single on-device acceptance run. The tool layer is CI-verified via a mockable injectable CDP connection / AIT source.
1019
+ Read-only tools only. Tools are registered in two tiers based on attach state — before attach, only the bootstrap tools (`start_attach`, `list_pages`) are visible; once a relay/local page attaches, the attach-dependent tools are registered dynamically in the same session via `notifications/tools/list_changed` (no session restart needed). The phone attach roundtrip is fully wired; all that remains is a single on-device acceptance run. The tool layer is CI-verified via a mockable injectable CDP connection / AIT source.
1036
1020
 
1037
1021
  Running `devtools-mcp` as a stdio server starts a local Chii relay on an OS-assigned port and opens a cloudflared quick tunnel, printing a public `wss://*.trycloudflare.com` URL and a QR code in the terminal (secrets/auth codes are never printed). When the phone enters the dog-food entry point, the in-app attach UI connects to the relay with that URL, and the agent reads console/network/page state via `chrome-devtools-mcp`-compatible tools — diagnosing regressions without anyone watching the phone.
1038
1022
 
@@ -1050,22 +1034,20 @@ Environments 3 and 4 (intoss-private relay) — start `devtools-mcp` as-is, then
1050
1034
  ```
1051
1035
 
1052
1036
  - Environment 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
1053
- - Environment 4 (LIVE relay, LIVE guard enabled): `start_debug({mode: 'relay-live', confirm: true})`
1054
-
1055
- **`start_debug(mode)` is the single in-session entry path.** `MCP_ENV=relay-live` remains only as a deprecated alias that seeds `liveIntent` at boot — in a new session, enter via `start_debug({mode: 'relay-live', confirm: true})`.
1037
+ **`start_debug(mode)` is the single in-session entry path.**
1056
1038
 
1057
1039
  | Tool | CDP / AIT backing | Description |
1058
1040
  |---|---|---|
1059
1041
  | `list_console_messages` | `Runtime.consoleAPICalled` | Recent console.log/warn/error messages (level, text, timestamp, args) |
1060
1042
  | `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | Recent XHR/fetch requests (url, method, status, timing) |
1061
1043
  | `list_pages` | Chii relay target list | Attached pages + tunnel status + wss URL |
1062
- | `build_attach_url` | (pure synthesis) | Splices `debug=1` + the relay URL into an `ait deploy --scheme-only` URL, prints a QR. Scanning the QR with the phone camera is the single entry path for env 2/3 (requires `list_pages` first) |
1044
+ | `start_attach` | (pure synthesis + attach wait) | Splices `debug=1` + the relay URL into an `ait deploy --scheme-only` URL, prints a QR, then waits in the same call until the phone attaches (QR generation and attach in one call). A `mode` arg can switch the session environment too, and the TOTP code is re-minted automatically during the wait. Entry tool for env 2 and 3 (bootstrap) — no `list_pages` needed first |
1063
1045
  | `get_dom_document` | `DOM.getDocument` | DOM tree read (structural/layout regression diagnosis) |
1064
1046
  | `take_snapshot` | `DOMSnapshot.captureSnapshot` | Page snapshot (documents + interned strings, visual regression) |
1065
1047
  | `take_screenshot` | `Page.captureScreenshot` | Page PNG screenshot (returned as an MCP image content block) |
1066
1048
  | `measure_safe_area` | `Runtime.evaluate` | Runs a safe-area probe on the attached page → returns normalized safe-area insets, viewport geometry, DPR, and User-Agent. Read-only. Use in a relay session to get ground-truth values for upgrading a viewport preset from extrapolated/placeholder to measured. Requires attach (`list_pages` first) |
1067
1049
  | `evaluate` | `Runtime.evaluate` | Evaluates an arbitrary JS expression on the attached page (returnByValue) and returns the result. **Not read-only** — the expression can have side effects (DOM mutations, SDK calls, state changes). Requires attach |
1068
- | `call_sdk` | `window.__sdkCall` bridge (via `Runtime.evaluate`) | Calls a dog-food SDK method via the `window.__sdkCall` bridge (exported by `@apps-in-toss/web-framework` in `__DEBUG_BUILD__` bundles only). **Not read-only** — SDK calls have side effects (navigation, payments, permissions, etc.). Hits the real SDK on env 3/4, mock SDK on env 1. Env 2 (PWA) does not inject the SDK — not available there. On env 4, `confirm: true` is required (LIVE guard). Requires attach. Returns `{ok,value}` / `{ok,error}` |
1050
+ | `call_sdk` | `window.__sdkCall` bridge (via `Runtime.evaluate`) | Calls a dog-food SDK method via the `window.__sdkCall` bridge (exported by `@apps-in-toss/web-framework` in `__DEBUG_BUILD__` bundles only). **Not read-only** — SDK calls have side effects (navigation, payments, permissions, etc.). Hits the real SDK on env 3, mock SDK on env 1. Env 2 (PWA) does not inject the SDK — not available there. Requires attach. Returns `{ok,value}` / `{ok,error}` |
1069
1051
  | `AIT.getSdkCallHistory` | AIT domain | SDK call trace (method, args, result/error, timestamp) |
1070
1052
  | `AIT.getMockState` | AIT domain | Mock state snapshot (`window.__ait`) |
1071
1053
  | `AIT.getOperationalEnvironment` | AIT domain | `getOperationalEnvironment()` + SDK version |
package/README.md CHANGED
@@ -60,31 +60,16 @@ pnpm dev:phone # AIT_TUNNEL=1 pnpm dev 와 동일
60
60
  ```bash
61
61
  devtools-mcp # MCP 서버 시작 → QR 출력
62
62
  # ait build && ait deploy --scheme-only
63
- # build_attach_url 호출 QR 스캔 토스 앱 로드 + relay attach
63
+ # start_attach(scheme_url) 호출 번으로 QR 생성 + 폰 attach까지 — QR 스캔하면 토스 앱 로드 + relay attach
64
64
  ```
65
65
 
66
66
  HMR 없음(토스 WebView cold-load만). 상세: [`docs/scenarios/env-3.md`](./docs/scenarios/env-3.md)
67
67
 
68
68
  ---
69
69
 
70
- **환경 4 — 배포된 앱 (LIVE)** (검수 통과 앱, HMR X, read-only debug)
71
-
72
- 검수를 통과하고 OPENED 상태인 실 출시 앱에 relay를 붙여 런타임을 관측합니다.
73
-
74
- ```bash
75
- devtools-mcp # MCP 서버 시작
76
- # Claude Code에서: start_debug({mode: 'relay-live', confirm: true}) ← LIVE guard 활성화
77
- # build_attach_url 호출 → QR 스캔 → LIVE 앱 로드 + relay attach
78
- # call_sdk / evaluate 는 confirm: true 필수 (LIVE guard — 실유저 영향)
79
- ```
80
-
81
- `start_debug({mode: 'relay-live', confirm: true})`가 LIVE guard를 활성화합니다. 상세: [`docs/scenarios/env-4.md`](./docs/scenarios/env-4.md)
82
-
83
- ---
84
-
85
70
  ## on-device 디버깅 한 줄 설정
86
71
 
87
- 환경 2·3·4에서 on-device CDP 디버깅을 활성화하려면 미니앱 entry(`main.tsx` 등)에 **한 줄**을 추가하세요:
72
+ 환경 2·3에서 on-device CDP 디버깅을 활성화하려면 미니앱 entry(`main.tsx` 등)에 **한 줄**을 추가하세요:
88
73
 
89
74
  ```ts
90
75
  // main.tsx (또는 미니앱 entry 최상단)
@@ -106,11 +91,11 @@ import '@ait-co/devtools/in-app/auto';
106
91
 
107
92
  **"QR 창이 안 열림"**
108
93
 
109
- `build_attach_url`을 먼저 호출하지 않았거나, GUI 없는 headless 환경이라 대시보드를 열 수 없는 경우입니다. 도구 결과에 텍스트 QR이 출력되므로 폰 카메라로 직접 스캔하세요. 로컬 GUI 환경에서는 대시보드가 자동으로 브라우저에 열립니다.
94
+ `start_attach`을 먼저 호출하지 않았거나, GUI 없는 headless 환경이라 대시보드를 열 수 없는 경우입니다. 도구 결과에 텍스트 QR이 출력되므로 폰 카메라로 직접 스캔하세요. 로컬 GUI 환경에서는 대시보드가 자동으로 브라우저에 열립니다.
110
95
 
111
96
  **"page 미attach" — list_pages가 빈 배열 반환**
112
97
 
113
- relay에 붙은 페이지가 없는 상태입니다. `build_attach_url` → QR 스캔 순서로 폰을 다시 진입시키세요. MCP 에러 메시지가 "페이지가 attach 안 됨. build_attach_url → QR 스캔."으로 뜨면 이 케이스입니다.
98
+ relay에 붙은 페이지가 없는 상태입니다. `start_attach` → QR 스캔 순서로 폰을 다시 진입시키세요. MCP 에러 메시지가 "페이지가 attach 안 됨. start_attach → QR 스캔."으로 뜨면 이 케이스입니다.
114
99
 
115
100
  **"tunnel down" — 터널 응답 없음 또는 timeout**
116
101
 
@@ -118,7 +103,7 @@ cloudflared quick tunnel은 수 시간 후 drop될 수 있습니다. `devtools-m
118
103
 
119
104
  **"page crash" — list_pages에 crashDetectedAt이 찍힘**
120
105
 
121
- 폰 측 페이지가 OOM·JS exception·native bridge crash로 죽은 상태입니다. 앱을 재실행 후 `build_attach_url` → QR 스캔으로 다시 attach하세요. (관련: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
106
+ 폰 측 페이지가 OOM·JS exception·native bridge crash로 죽은 상태입니다. 앱을 재실행 후 `start_attach` → QR 스캔으로 다시 attach하세요. (관련: [#265](https://github.com/apps-in-toss-community/devtools/issues/265))
122
107
 
123
108
  **"SDK 부재" — window.__sdkCall 미주입**
124
109
 
@@ -126,7 +111,7 @@ cloudflared quick tunnel은 수 시간 후 drop될 수 있습니다. `devtools-m
126
111
 
127
112
  **"QR 스캔했는데 인증 실패" — TOTP 만료**
128
113
 
129
- `AIT_DEBUG_TOTP_SECRET` 설정 시 `build_attach_url`이 반환하는 attachUrl에는 TOTP 코드(`at=`)가 자동으로 포함됩니다. 코드 1개는 30초 창이고, relay는 만료 후 ~3분(±6 step) 이내 소급을 허용합니다. 응답의 `totp.expiresAt`로부터 ~3분 이상 지난 스캔하면 relay가 인증을 거부합니다. 해결: `build_attach_url`을 재호출해 새 URL과 QR을 발급받으세요.
114
+ `AIT_DEBUG_TOTP_SECRET` 설정 시 `start_attach`이 반환하는 attachUrl에는 TOTP 코드(`at=`)가 자동으로 포함됩니다. 코드 1개는 30초 창이고, relay는 만료 후 ~3분(±6 step) 이내 소급을 허용합니다. `start_attach`은 attach를 기다리는 동안 코드가 만료 창에 가까워지면 호출 안에서 코드를 자동 재발행하므로(재발행 횟수는 응답의 `totp.reminted`로 노출), 대기 중에는 보통 재호출이 필요 없습니다. 그래도 만료된 QR을 스캔해 relay가 인증을 거부하면 `start_attach`을 재호출해 새 URL과 QR을 발급받으세요.
130
115
 
131
116
  ---
132
117
 
@@ -1009,15 +994,14 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
1009
994
  |---|---|---|---|---|
1010
995
  | `--target=mobile` (env 2) | `devtools-mcp` → `start_debug({mode:'relay-sandbox'})` | `AIT_RELAY_BASE_URL`, `AIT_TUNNEL_BASE_URL` | 실기기 Safari/WebKit PWA (외부 Chii relay + cloudflared 터널, 환경 2) | console/network/page + DOM/snapshot/screenshot |
1011
996
  | `--mode=debug --target=relay` (기본값, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | 폰 안 dog-food 번들 (CDP/Chii relay + cloudflared 터널, 환경 3) | 동일 + `AIT.*` |
1012
- | `--mode=debug --target=relay` LIVE (env 4) | `devtools-mcp` → `start_debug({mode: 'relay-live', confirm: true})` | — (**환경 4 LIVE guard**) | LIVE 배포 앱 (환경 4) — `call_sdk`/`evaluate`에 `confirm: true` 필요 | 동일 |
1013
997
  | `--mode=debug --target=local` (env 1) | `devtools-mcp --target=local` | `MCP_ENV=mock` (자동) | MCP가 직접 기동한 로컬 Chromium (CDP direct-attach, relay 불필요, 환경 1) | 동일 |
1014
998
  | `--mode=dev` | `devtools-mcp --mode=dev` | `MCP_ENV=mock` (자동) | 실행 중인 Vite dev server의 mock state (AIT.* 전용, CDP 없음) | `AIT.*` (+ `devtools_get_mock_state` alias) |
1015
999
 
1016
- `--target=local`은 `AIT_DEVTOOLS_URL`(기본 `http://localhost:5173`)을 열고 로컬 Chromium에 CDP direct-attach합니다 — relay나 터널이 필요하지 않습니다. `--mode=dev`는 Vite dev server의 mock-state HTTP endpoint를 읽으며 CDP tool은 제공하지 않습니다. 세션 내 환경 전환은 `start_debug(mode)` 한 번으로 처리됩니다: `relay-sandbox`(env 2 PWA), `relay-staging`(env 3 dogfood), `relay-live`(env 4 LIVE guard 활성화, `confirm: true` 필수), `local-browser`(env 1).
1000
+ `--target=local`은 `AIT_DEVTOOLS_URL`(기본 `http://localhost:5173`)을 열고 로컬 Chromium에 CDP direct-attach합니다 — relay나 터널이 필요하지 않습니다. `--mode=dev`는 Vite dev server의 mock-state HTTP endpoint를 읽으며 CDP tool은 제공하지 않습니다. 세션 내 환경 전환은 `start_debug(mode)` 한 번으로 처리됩니다: `relay-sandbox`(env 2 PWA), `relay-staging`(env 3 dogfood), `local-browser`(env 1).
1017
1001
 
1018
1002
  #### 환경 2 (실기기 PWA CDP) — `--target=mobile`
1019
1003
 
1020
- 토스 검수 없이 실기기 WebKit 엔진에서 CDP 디버깅이 가능한 모드입니다. [`tunnel:{cdp:true}`](#tunnel-옵션)를 켠 Vite dev server가 앱 HTTP 터널과 Chii relay 터널을 두 개 띄우고, MCP는 그 relay에 붙어 `build_attach_url` → launcher QR을 제공합니다.
1004
+ 토스 검수 없이 실기기 WebKit 엔진에서 CDP 디버깅이 가능한 모드입니다. [`tunnel:{cdp:true}`](#tunnel-옵션)를 켠 Vite dev server가 앱 HTTP 터널과 Chii relay 터널을 두 개 띄우고, MCP는 그 relay에 붙어 `start_attach` → launcher QR을 제공합니다.
1021
1005
 
1022
1006
  **진입 절차:**
1023
1007
 
@@ -1048,7 +1032,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
1048
1032
  3. Claude Code 세션에서 진입:
1049
1033
  ```
1050
1034
  start_debug({mode: 'relay-sandbox'})
1051
- build_attach_url()
1035
+ start_attach()
1052
1036
  ```
1053
1037
  QR을 폰 카메라로 스캔하면 launcher PWA가 앱을 프레임에 열고 Chii target.js를 주입합니다.
1054
1038
 
@@ -1063,7 +1047,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
1063
1047
  실기기 relay 디버깅 루프(dog-food 빌드 → QR 스캔 → relay attach)의 단계별 절차와 복구 방법은 **[`docs/dogfood-relay-loop.md`](./docs/dogfood-relay-loop.md)** 를 참고하세요. crash가 발생한 경우 — `list_pages.crashDetectedAt`, iOS Console.app `.ips` 분석, redact 절차를 포함한 원인 추적 절차는 **[`docs/crash-triage.md`](./docs/crash-triage.md)** 를 참고하세요.
1064
1048
 
1065
1049
  read-only tool만 노출합니다. 도구는 attach 상태에 따라 2단계로 등록됩니다 — attach 전에는 bootstrap
1066
- 도구(`build_attach_url`·`list_pages`)만 보이고, 릴레이/로컬 페이지가 attach되면 `notifications/tools/list_changed`로
1050
+ 도구(`start_attach`·`list_pages`)만 보이고, 릴레이/로컬 페이지가 attach되면 `notifications/tools/list_changed`로
1067
1051
  attach 의존 도구가 같은 세션에서 동적 등록됩니다(세션 재시작 불필요). 폰 attach 라운드트립은 fully wired
1068
1052
  상태이며 남은 것은 실기기 acceptance 한 번뿐입니다. tool 계층은 주입 가능한 CDP 연결 / AIT 소스를 mock해
1069
1053
  CI에서 검증됩니다.
@@ -1088,22 +1072,20 @@ tunnel로 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR을 터미
1088
1072
  ```
1089
1073
 
1090
1074
  - 환경 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
1091
- - 환경 4 (LIVE relay, LIVE guard 활성화): `start_debug({mode: 'relay-live', confirm: true})`
1092
-
1093
- **세션 내 환경 전환은 `start_debug(mode)`가 단일 진입 경로**입니다. `MCP_ENV=relay-live`는 부팅 시 `liveIntent`를 미리 시드하는 deprecated 별칭으로만 남아 있습니다 — 새 세션에서는 `start_debug({mode: 'relay-live', confirm: true})`로 진입하세요.
1075
+ **세션 환경 전환은 `start_debug(mode)`가 단일 진입 경로**입니다.
1094
1076
 
1095
1077
  | Tool | CDP / AIT 백킹 | 설명 |
1096
1078
  |---|---|---|
1097
1079
  | `list_console_messages` | `Runtime.consoleAPICalled` | 최근 console.log/warn/error 메시지 (level, text, timestamp, args) |
1098
1080
  | `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | 최근 XHR/fetch 요청 (url, method, status, timing) |
1099
1081
  | `list_pages` | Chii 릴레이 target 목록 | attach된 페이지 + tunnel 상태 + wss URL |
1100
- | `build_attach_url` | (순수 합성) | `ait deploy --scheme-only` URL에 `debug=1`+릴레이 URL을 끼워 self-attach deep-link 합성 QR을 터미널 출력. QR 카메라로 스캔하는 것이 환경 2·3 단일 진입 경로 (`list_pages` 먼저 필요) |
1082
+ | `start_attach` | (순수 합성 + attach 대기) | `ait deploy --scheme-only` URL에 `debug=1`+릴레이 URL을 끼워 self-attach deep-link 합성하고 QR을 출력한 뒤, 폰이 attach될 때까지 같은 호출 안에서 대기한다(QR 생성·attach가 호출). `mode`로 세션 환경을 함께 전환할 수 있고, 대기 중 TOTP 코드를 자동 재발행한다. 환경 2·3 진입 도구(bootstrap) `list_pages` 선행 불필요 |
1101
1083
  | `get_dom_document` | `DOM.getDocument` | DOM 트리 read (구조/레이아웃 회귀 진단) |
1102
1084
  | `take_snapshot` | `DOMSnapshot.captureSnapshot` | 페이지 스냅샷 (documents + interned strings, 시각 회귀 진단) |
1103
1085
  | `take_screenshot` | `Page.captureScreenshot` | 페이지 PNG 스크린샷 (MCP image content block 반환) |
1104
1086
  | `measure_safe_area` | `Runtime.evaluate` | attach된 페이지에서 safe-area 프로브 실행 → 정규화된 safe-area inset·뷰포트 geometry·DPR·User-Agent 반환. read-only. relay 세션(폰 attach)에서 viewport preset을 extrapolated/placeholder→measured로 승급할 ground truth 수집용. attach 필요 (`list_pages` 먼저) |
1105
1087
  | `evaluate` | `Runtime.evaluate` | attach된 페이지에서 임의 JS 표현식 평가(returnByValue) → 결과 반환. **read-only 아님** — 표현식이 부작용(DOM 변경·SDK 호출·상태 변경)을 일으킬 수 있음. attach 필요 |
1106
- | `call_sdk` | `window.__sdkCall` 브리지 (`Runtime.evaluate` 경유) | dog-food SDK 메서드를 `window.__sdkCall` 브리지로 호출 (`@apps-in-toss/web-framework`가 `__DEBUG_BUILD__` 번들에서만 export). **read-only 아님** — SDK 호출은 부작용(내비게이션·결제·권한 등). 환경 3·4(실기기 relay)에선 실 SDK, 환경 1(로컬 mock)에선 mock SDK. 환경 2(PWA)는 SDK 미주입으로 사용 불가. 환경 4에서는 `confirm: true` 필수(LIVE guard). attach 필요. `{ok,value}` / `{ok,error}` 반환 |
1088
+ | `call_sdk` | `window.__sdkCall` 브리지 (`Runtime.evaluate` 경유) | dog-food SDK 메서드를 `window.__sdkCall` 브리지로 호출 (`@apps-in-toss/web-framework`가 `__DEBUG_BUILD__` 번들에서만 export). **read-only 아님** — SDK 호출은 부작용(내비게이션·결제·권한 등). 환경 3(실기기 relay)에선 실 SDK, 환경 1(로컬 mock)에선 mock SDK. 환경 2(PWA)는 SDK 미주입으로 사용 불가. attach 필요. `{ok,value}` / `{ok,error}` 반환 |
1107
1089
  | `AIT.getSdkCallHistory` | AIT 도메인 | SDK 호출 trace (method, args, result/error, timestamp) |
1108
1090
  | `AIT.getMockState` | AIT 도메인 | mock state 스냅샷 (`window.__ait`) |
1109
1091
  | `AIT.getOperationalEnvironment` | AIT 도메인 | `getOperationalEnvironment()` + SDK 버전 |
@@ -1 +1 @@
1
- {"version":3,"file":"auto.d.ts","names":[],"sources":["../../src/in-app/auto.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6IA;;;;;AAkCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAhGQ,MAAA;EAAA,UACI,MAAA;;;;;;;;;;IAUR,KAAA,GAAQ,MAAA;;;;;;;;;IAUR,SAAA,IACE,IAAA,aACG,IAAA,gBACA,OAAA;MAAU,EAAA;MAAa,KAAA;MAAiB,KAAA;IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBAsCjC,eAAA,CAAA;;;;;;;;;;;;;;iBAkCA,cAAA,CACd,KAAA,YACA,SAAA"}
1
+ {"version":3,"file":"auto.d.ts","names":[],"sources":["../../src/in-app/auto.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AA8IA;;;;;AAkCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAhGQ,MAAA;EAAA,UACI,MAAA;;;;;;;;;;IAUR,KAAA,GAAQ,MAAA;;;;;;;;;IAUR,SAAA,IACE,IAAA,aACG,IAAA,gBACA,OAAA;MAAU,EAAA;MAAa,KAAA;MAAiB,KAAA;IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBAsCjC,eAAA,CAAA;;;;;;;;;;;;;;iBAkCA,cAAA,CACd,KAAA,YACA,SAAA"}
@@ -151,6 +151,42 @@ function isTrycloudflareHost(hostname) {
151
151
  return hostname.endsWith(TRYCLOUDFLARE_HOST_SUFFIX);
152
152
  }
153
153
  /**
154
+ * Returns true when the hostname is a localhost/loopback address.
155
+ * Allowed: `localhost`, `127.x.x.x` (full RFC 5735 loopback block), `[::1]`,
156
+ * `0.0.0.0`, `*.localhost`.
157
+ *
158
+ * Security note: `hostname.startsWith('127.')` is intentionally NOT used —
159
+ * that pattern would accept `127.evil.com`, which starts with "127." but is an
160
+ * attacker-controlled hostname, not a loopback address. Instead, the 127/8
161
+ * loopback block is matched with a strict numeric-quad regex so only valid
162
+ * dotted-decimal IPv4 in the 127.x.x.x range pass (#665 작업 A fix).
163
+ */
164
+ function isLocalhostHost(hostname) {
165
+ if (hostname === "localhost" || hostname === "0.0.0.0") return true;
166
+ if (hostname === "[::1]") return true;
167
+ if (/^127\.\d+\.\d+\.\d+$/.test(hostname)) return true;
168
+ if (hostname.endsWith(".localhost")) return true;
169
+ return false;
170
+ }
171
+ /**
172
+ * Positive-allowlist kill-switch (#665): returns true when the hostname is a
173
+ * known debug-allowed host. The debug surface is ONLY active on:
174
+ * - localhost / loopback (env 1 desktop dev)
175
+ * - *.trycloudflare.com (env 2 PWA tunnel)
176
+ * - *.private-apps.tossmini.com (env 3 dog-food)
177
+ *
178
+ * Any other host (including apps.tossmini.com — the former env 4 LIVE host)
179
+ * is silently blocked. This is a positive allowlist — unlisted hosts never
180
+ * had debug surface regardless, but this function makes it explicit and
181
+ * auditable in a single place.
182
+ *
183
+ * SECRET-HANDLING: the hostname value MUST NOT be logged or included in any
184
+ * error reason string — only benign labels ('host not in allowlist') are safe.
185
+ */
186
+ function isDebugAllowedHost(hostname) {
187
+ return isLocalhostHost(hostname) || isTrycloudflareHost(hostname) || isPrivateAppsHost(hostname);
188
+ }
189
+ /**
154
190
  * Pure function that evaluates the runtime debug activation layers (B and C).
155
191
  *
156
192
  * Has no side effects. The input is explicit. Returns a discriminated union
@@ -173,12 +209,13 @@ function isTrycloudflareHost(hostname) {
173
209
  */
174
210
  function evaluateDebugGate(input) {
175
211
  const isTunnel = isTrycloudflareHost(input.hostname);
176
- if (!isPrivateAppsHost(input.hostname) && !isTunnel) return {
212
+ const isLocal = isLocalhostHost(input.hostname);
213
+ if (!isDebugAllowedHost(input.hostname)) return {
177
214
  attach: false,
178
215
  reason: "host"
179
216
  };
180
217
  let deploymentId = "";
181
- if (!isTunnel) {
218
+ if (!isTunnel && !isLocal) {
182
219
  deploymentId = input.searchParams.get("_deploymentId") ?? "";
183
220
  if (deploymentId === "") return {
184
221
  attach: false,
@@ -688,7 +725,7 @@ function shouldActivate(isDev = detectDevSignal(), searchStr = typeof window !==
688
725
  const params = new URLSearchParams(searchStr);
689
726
  return params.get("debug") === "1" || params.has("relay");
690
727
  }
691
- if (!shouldActivate()) {} else {
728
+ if (!shouldActivate()) {} else if (typeof window === "undefined" || !isDebugAllowedHost(window.location.hostname)) {} else {
692
729
  maybeAttach();
693
730
  import("@apps-in-toss/web-framework").then((sdk) => {
694
731
  if (typeof window === "undefined") return;