@ait-co/devtools 0.1.103 → 0.1.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@ait-co/devtools)](https://www.npmjs.com/package/@ait-co/devtools) [![license](https://img.shields.io/badge/license-BSD--3--Clause-blue)](./LICENSE)
6
6
 
7
- ![@ait-co/devtools — SDK mock + DevTools panel for Apps In Toss mini-apps](./assets/og/image.png)
7
+ ![@ait-co/devtools — mock SDK + DevTools panel for Apps in Toss mini-apps](./assets/og/image.png)
8
8
 
9
9
  A mock library for the `@apps-in-toss/web-framework` SDK. Imports of `@apps-in-toss/webview-bridge` are intercepted by the unplugin too (only the high-level SDK functions are exposed — bridge primitives are not). (2.x packages `@apps-in-toss/web-bridge` and `@apps-in-toss/web-analytics` are supported for back-compat.)
10
10
 
@@ -55,7 +55,7 @@ One-time prerequisite: add `https://devtools.aitc.dev/launcher/` to your phone's
55
55
 
56
56
  **Environment 3 — intoss-private** (Toss WebView, HMR off, debug only)
57
57
 
58
- Load a dogfood bundle in the real Toss app WebView and debug it via the MCP relay.
58
+ Load a dog-food bundle in the real Toss app WebView and debug it via the MCP relay.
59
59
 
60
60
  ```bash
61
61
  devtools-mcp # start MCP server → QR printed in terminal
@@ -100,7 +100,7 @@ What this single line does:
100
100
 
101
101
  For environments 3 and 4 (intoss-private relay), the relay QR deep-link carries `?debug=1&relay=<wss>` query params, so this one line is all the wiring you need. Environment 2 (PWA, `tunnel: { cdp: true }`) works the same way.
102
102
 
103
- > For dogfood builds with TOTP authentication, inject `__DEBUG_TOTP_SECRET__` via your build define and use `@ait-co/devtools/in-app` directly with `evaluateDebugGate({ verifyTotpCode })` + `maybeAttach()`. `in-app/auto` does not inject a TOTP verifier, so Layer C3 is disabled.
103
+ > For dog-food builds with TOTP authentication, inject `__DEBUG_TOTP_SECRET__` via your build define and use `@ait-co/devtools/in-app` directly with `evaluateDebugGate({ verifyTotpCode })` + `maybeAttach()`. `in-app/auto` does not inject a TOTP verifier, so Layer C3 is disabled.
104
104
 
105
105
  ## Five common problems
106
106
 
@@ -144,7 +144,7 @@ devtools runs two npm dist-tags off the same code at once. Pick the channel that
144
144
 
145
145
  | Channel | Install | web-framework peer |
146
146
  |---|---|---|
147
- | **stable** (`latest`, default) | `pnpm add -D @ait-co/devtools` | `>=2.6.0 <2.7.0` (2.x) |
147
+ | **stable** (`latest`, default) | `pnpm add -D @ait-co/devtools` | `>=2.6.0 <3.0.0` (2.x) |
148
148
  | **beta** | `pnpm add -D @ait-co/devtools@beta` | `>=3.0.0-beta <4.0.0` (3.0 line) |
149
149
 
150
150
  - On web-framework **2.x**, the default install (stable) is all you need.
@@ -978,7 +978,7 @@ A local browser (env 1) and a phone Toss WebView (env 2/3) both speak CDP, so ev
978
978
  | Mode + target | Invocation | Env vars | Target | Tools |
979
979
  |---|---|---|---|---|
980
980
  | `--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
- | `--mode=debug --target=relay` (default, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | Dogfood bundle on a phone (CDP/Chii relay + cloudflared tunnel, env 3) | same + `AIT.*` |
981
+ | `--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
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
983
  | `--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
984
  | `--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) |
@@ -1030,11 +1030,11 @@ Debug on a real phone using Safari/WebKit without Toss review. The Vite dev serv
1030
1030
 
1031
1031
  ### Debug mode (CDP via Chii)
1032
1032
 
1033
- For a step-by-step walkthrough of the on-device relay debug loop (dogfood 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).
1033
+ 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
1034
 
1035
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.
1036
1036
 
1037
- 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 dogfood 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.
1037
+ 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
1038
 
1039
1039
  Environments 3 and 4 (intoss-private relay) — start `devtools-mcp` as-is, then enter via `start_debug(mode)`:
1040
1040
 
@@ -1049,7 +1049,7 @@ Environments 3 and 4 (intoss-private relay) — start `devtools-mcp` as-is, then
1049
1049
  }
1050
1050
  ```
1051
1051
 
1052
- - Environment 3 (dogfood relay): `start_debug({mode: 'relay-staging'})`
1052
+ - Environment 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
1053
1053
  - Environment 4 (LIVE relay, LIVE guard enabled): `start_debug({mode: 'relay-live', confirm: true})`
1054
1054
 
1055
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})`.
@@ -1065,7 +1065,7 @@ Environments 3 and 4 (intoss-private relay) — start `devtools-mcp` as-is, then
1065
1065
  | `take_screenshot` | `Page.captureScreenshot` | Page PNG screenshot (returned as an MCP image content block) |
1066
1066
  | `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
1067
  | `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 dogfood 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}` |
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}` |
1069
1069
  | `AIT.getSdkCallHistory` | AIT domain | SDK call trace (method, args, result/error, timestamp) |
1070
1070
  | `AIT.getMockState` | AIT domain | Mock state snapshot (`window.__ait`) |
1071
1071
  | `AIT.getOperationalEnvironment` | AIT domain | `getOperationalEnvironment()` + SDK version |
@@ -1142,7 +1142,7 @@ Returns the full current mock state (permissions, location, auth, network, IAP,
1142
1142
  | `@ait-co/devtools/unplugin` | Bundler plugin (.vite, .webpack, .rspack, .esbuild, .rollup) |
1143
1143
  | `@ait-co/devtools/mcp/server` | Dev-mode MCP stdio server function (Node.js) |
1144
1144
  | `@ait-co/devtools/mcp/cli` | `devtools-mcp` bin entry point (debug / dev mode, Node.js) |
1145
- | `@ait-co/devtools/in-app` | In-app debug attach — runtime gate (layers B/C) + Chii target.js injection. The consumer wraps the import in `if (__DEBUG_BUILD__)` so it is DCE'd from release builds — dogfood builds only |
1145
+ | `@ait-co/devtools/in-app` | In-app debug attach — runtime gate (layers B/C) + Chii target.js injection. The consumer wraps the import in `if (__DEBUG_BUILD__)` so it is DCE'd from release builds — dog-food builds only |
1146
1146
  | `@ait-co/devtools/in-app/auto` | Self-gating side-effect entry — a single `import '@ait-co/devtools/in-app/auto'` line wires attach + SDK bridge. Active only when `?debug=1` / `?relay=` are in the URL or it is a DEV build; stays dormant on normal production loads. See the [section above](#on-device-debugging-in-one-line) |
1147
1147
 
1148
1148
  ## License
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@ait-co/devtools)](https://www.npmjs.com/package/@ait-co/devtools) [![license](https://img.shields.io/badge/license-BSD--3--Clause-blue)](./LICENSE)
6
6
 
7
- ![@ait-co/devtools — SDK mock + DevTools panel for Apps In Toss mini-apps](./assets/og/image.png)
7
+ ![@ait-co/devtools — mock SDK + DevTools panel for Apps in Toss mini-apps](./assets/og/image.png)
8
8
 
9
9
  `@apps-in-toss/web-framework` SDK의 mock 라이브러리입니다. `@apps-in-toss/webview-bridge` import도 unplugin이 함께 인터셉트합니다(high-level SDK 함수만 노출 — bridge primitive는 미노출). (2.x의 `@apps-in-toss/web-bridge`, `@apps-in-toss/web-analytics`도 back-compat으로 지원.)
10
10
 
@@ -55,7 +55,7 @@ pnpm dev:phone # AIT_TUNNEL=1 pnpm dev 와 동일
55
55
 
56
56
  **환경 3 — intoss-private** (토스 WebView, HMR X, debug 전용)
57
57
 
58
- 실기기 토스 앱 WebView에서 dogfood 번들을 로드하고 MCP relay로 디버깅합니다.
58
+ 실기기 토스 앱 WebView에서 dog-food 번들을 로드하고 MCP relay로 디버깅합니다.
59
59
 
60
60
  ```bash
61
61
  devtools-mcp # MCP 서버 시작 → QR 출력
@@ -100,7 +100,7 @@ import '@ait-co/devtools/in-app/auto';
100
100
 
101
101
  환경 3·4(intoss-private relay) 빌드는 relay QR deep-link가 `?debug=1&relay=<wss>` 파라미터를 실어 보내므로, 이 한 줄만 있으면 별도 게이트 코드가 필요 없습니다. 환경 2(PWA, `tunnel: { cdp: true }`)도 동일하게 동작합니다.
102
102
 
103
- > TOTP 인증이 필요한 dogfood 빌드는 빌드 define으로 `__DEBUG_TOTP_SECRET__`을 주입하고 `@ait-co/devtools/in-app`을 직접 import해 `evaluateDebugGate({ verifyTotpCode })` + `maybeAttach()`를 사용하세요. `in-app/auto`는 TOTP verifier를 주입하지 않으므로 C3 레이어가 비활성화됩니다.
103
+ > TOTP 인증이 필요한 dog-food 빌드는 빌드 define으로 `__DEBUG_TOTP_SECRET__`을 주입하고 `@ait-co/devtools/in-app`을 직접 import해 `evaluateDebugGate({ verifyTotpCode })` + `maybeAttach()`를 사용하세요. `in-app/auto`는 TOTP verifier를 주입하지 않으므로 C3 레이어가 비활성화됩니다.
104
104
 
105
105
  ## 자주 겪는 문제 5가지
106
106
 
@@ -144,7 +144,7 @@ devtools는 같은 코드에서 두 개의 npm dist-tag를 동시에 운영합
144
144
 
145
145
  | 채널 | 설치 | web-framework peer |
146
146
  |---|---|---|
147
- | **stable** (`latest`, 기본) | `pnpm add -D @ait-co/devtools` | `>=2.6.0 <2.7.0` (2.x) |
147
+ | **stable** (`latest`, 기본) | `pnpm add -D @ait-co/devtools` | `>=2.6.0 <3.0.0` (2.x) |
148
148
  | **beta** | `pnpm add -D @ait-co/devtools@beta` | `>=3.0.0-beta <4.0.0` (3.0 라인) |
149
149
 
150
150
  - web-framework **2.x**를 쓰면 위 기본 설치(stable)면 됩니다.
@@ -1008,7 +1008,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
1008
1008
  | 모드 + 타깃 | 호출 | 환경 변수 | 대상 | tool |
1009
1009
  |---|---|---|---|---|
1010
1010
  | `--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
- | `--mode=debug --target=relay` (기본값, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | 폰 안 dogfood 번들 (CDP/Chii relay + cloudflared 터널, 환경 3) | 동일 + `AIT.*` |
1011
+ | `--mode=debug --target=relay` (기본값, env 3) | `devtools-mcp` → `start_debug({mode: 'relay-staging'})` | — | 폰 안 dog-food 번들 (CDP/Chii relay + cloudflared 터널, 환경 3) | 동일 + `AIT.*` |
1012
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
1013
  | `--mode=debug --target=local` (env 1) | `devtools-mcp --target=local` | `MCP_ENV=mock` (자동) | MCP가 직접 기동한 로컬 Chromium (CDP direct-attach, relay 불필요, 환경 1) | 동일 |
1014
1014
  | `--mode=dev` | `devtools-mcp --mode=dev` | `MCP_ENV=mock` (자동) | 실행 중인 Vite dev server의 mock state (AIT.* 전용, CDP 없음) | `AIT.*` (+ `devtools_get_mock_state` alias) |
@@ -1060,7 +1060,7 @@ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)]
1060
1060
 
1061
1061
  ### Debug 모드 (CDP via Chii)
1062
1062
 
1063
- 실기기 relay 디버깅 루프(dogfood 빌드 → 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)** 를 참고하세요.
1063
+ 실기기 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
1064
 
1065
1065
  read-only tool만 노출합니다. 도구는 attach 상태에 따라 2단계로 등록됩니다 — attach 전에는 bootstrap
1066
1066
  도구(`build_attach_url`·`list_pages`)만 보이고, 릴레이/로컬 페이지가 attach되면 `notifications/tools/list_changed`로
@@ -1070,7 +1070,7 @@ CI에서 검증됩니다.
1070
1070
 
1071
1071
  `devtools-mcp`를 stdio로 실행하면 로컬 Chii 릴레이를 OS가 할당한 포트에 띄우고 cloudflared quick
1072
1072
  tunnel로 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR을 터미널에 출력합니다(시크릿/인증
1073
- 코드는 출력하지 않습니다). 폰이 dogfood 진입 시 in-app attach UI가 그 URL로 릴레이에 붙으면,
1073
+ 코드는 출력하지 않습니다). 폰이 dog-food 진입 시 in-app attach UI가 그 URL로 릴레이에 붙으면,
1074
1074
  에이전트가 `chrome-devtools-mcp` 호환 tool로 console/network/page 상태를 read합니다. 사람이 폰을
1075
1075
  지켜볼 필요 없이 회귀를 단독 진단하는 것이 목표입니다.
1076
1076
 
@@ -1087,7 +1087,7 @@ tunnel로 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR을 터미
1087
1087
  }
1088
1088
  ```
1089
1089
 
1090
- - 환경 3 (dogfood relay): `start_debug({mode: 'relay-staging'})`
1090
+ - 환경 3 (dog-food relay): `start_debug({mode: 'relay-staging'})`
1091
1091
  - 환경 4 (LIVE relay, LIVE guard 활성화): `start_debug({mode: 'relay-live', confirm: true})`
1092
1092
 
1093
1093
  **세션 내 환경 전환은 `start_debug(mode)`가 단일 진입 경로**입니다. `MCP_ENV=relay-live`는 부팅 시 `liveIntent`를 미리 시드하는 deprecated 별칭으로만 남아 있습니다 — 새 세션에서는 `start_debug({mode: 'relay-live', confirm: true})`로 진입하세요.
@@ -1103,7 +1103,7 @@ tunnel로 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR을 터미
1103
1103
  | `take_screenshot` | `Page.captureScreenshot` | 페이지 PNG 스크린샷 (MCP image content block 반환) |
1104
1104
  | `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
1105
  | `evaluate` | `Runtime.evaluate` | attach된 페이지에서 임의 JS 표현식 평가(returnByValue) → 결과 반환. **read-only 아님** — 표현식이 부작용(DOM 변경·SDK 호출·상태 변경)을 일으킬 수 있음. attach 필요 |
1106
- | `call_sdk` | `window.__sdkCall` 브리지 (`Runtime.evaluate` 경유) | dogfood 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}` 반환 |
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}` 반환 |
1107
1107
  | `AIT.getSdkCallHistory` | AIT 도메인 | SDK 호출 trace (method, args, result/error, timestamp) |
1108
1108
  | `AIT.getMockState` | AIT 도메인 | mock state 스냅샷 (`window.__ait`) |
1109
1109
  | `AIT.getOperationalEnvironment` | AIT 도메인 | `getOperationalEnvironment()` + SDK 버전 |
@@ -1182,7 +1182,7 @@ export default {
1182
1182
  | `@ait-co/devtools/unplugin` | 번들러 플러그인 (.vite, .webpack, .rspack, .esbuild, .rollup) |
1183
1183
  | `@ait-co/devtools/mcp/server` | dev-mode MCP stdio server 함수 (Node.js) |
1184
1184
  | `@ait-co/devtools/mcp/cli` | `devtools-mcp` bin 진입점 (debug / dev 모드, Node.js) |
1185
- | `@ait-co/devtools/in-app` | In-app debug attach — 런타임 gate(layer B·C) + Chii target.js 주입. 소비자가 `if (__DEBUG_BUILD__)`로 import를 감싸 release 빌드에서 DCE — dogfood 빌드 전용 |
1185
+ | `@ait-co/devtools/in-app` | In-app debug attach — 런타임 gate(layer B·C) + Chii target.js 주입. 소비자가 `if (__DEBUG_BUILD__)`로 import를 감싸 release 빌드에서 DCE — dog-food 빌드 전용 |
1186
1186
  | `@ait-co/devtools/in-app/auto` | Self-gating side-effect entry — `import '@ait-co/devtools/in-app/auto'` 한 줄로 attach + SDK 브리지 설치. URL 파라미터(`?debug=1` / `?relay=`) 또는 DEV 빌드에서만 활성화, 일반 프로덕션 로드는 dormant. [위 섹션](#on-device-디버깅-한-줄-설정) 참고 |
1187
1187
 
1188
1188
  ## 라이센스
package/dist/mcp/cli.js CHANGED
@@ -2164,6 +2164,7 @@ const en = {
2164
2164
  "dashboard.tunnel.down": "Disconnected",
2165
2165
  "dashboard.attach.section": "Attach QR",
2166
2166
  "dashboard.attach.hint": "Call the build_attach_url MCP tool to show the QR here.",
2167
+ "dashboard.attach.tunnelDown": "Relay disconnected — this QR is no longer valid. Restart the relay, then regenerate the QR.",
2167
2168
  "dashboard.pages.section": "Connected Pages",
2168
2169
  "dashboard.pages.empty": "No attached pages",
2169
2170
  "dashboard.url.copy": "Copy",
@@ -2400,6 +2401,7 @@ const tables = {
2400
2401
  "dashboard.tunnel.down": "끊어짐",
2401
2402
  "dashboard.attach.section": "Attach QR",
2402
2403
  "dashboard.attach.hint": "build_attach_url MCP tool을 호출하면 QR이 여기에 표시됩니다.",
2404
+ "dashboard.attach.tunnelDown": "relay 연결이 끊겼습니다 — 이 QR은 더 이상 유효하지 않습니다. relay를 재시작한 뒤 QR을 다시 생성하세요.",
2403
2405
  "dashboard.pages.section": "연결된 Pages",
2404
2406
  "dashboard.pages.empty": "attach된 페이지 없음",
2405
2407
  "dashboard.url.copy": "복사",
@@ -2553,6 +2555,7 @@ img.qr {
2553
2555
  }
2554
2556
  .copy-btn:hover { background: #30363d; }
2555
2557
  .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2558
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2556
2559
  .inspector-link {
2557
2560
  display: inline-block; margin-top: 0.5rem;
2558
2561
  padding: 0.45rem 1rem; border-radius: 6px;
@@ -2595,6 +2598,8 @@ img.qr {
2595
2598
  display: block; margin: 0 auto;
2596
2599
  }
2597
2600
  section { width: 100%; max-width: 480px; }
2601
+ .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2602
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2598
2603
  h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
2599
2604
  ol, ul { margin: 0; padding-left: 1.25rem; }
2600
2605
  li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
@@ -2653,6 +2658,8 @@ img.qr {
2653
2658
  display: block; margin: 0 auto;
2654
2659
  }
2655
2660
  section { width: 100%; max-width: 480px; }
2661
+ .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2662
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2656
2663
  h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
2657
2664
  ol, ul { margin: 0; padding-left: 1.25rem; }
2658
2665
  li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
@@ -2729,6 +2736,7 @@ img.qr {
2729
2736
  }
2730
2737
  .copy-btn:hover { background: #30363d; }
2731
2738
  .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2739
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2732
2740
  .inspector-link {
2733
2741
  display: inline-block; margin-top: 0.5rem;
2734
2742
  padding: 0.45rem 1rem; border-radius: 6px;
@@ -2771,6 +2779,8 @@ img.qr {
2771
2779
  display: block; margin: 0 auto;
2772
2780
  }
2773
2781
  section { width: 100%; max-width: 480px; }
2782
+ .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2783
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2774
2784
  h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
2775
2785
  ol, ul { margin: 0; padding-left: 1.25rem; }
2776
2786
  li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
@@ -2829,6 +2839,8 @@ img.qr {
2829
2839
  display: block; margin: 0 auto;
2830
2840
  }
2831
2841
  section { width: 100%; max-width: 480px; }
2842
+ .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2843
+ .hint.error { color: #f85149; opacity: 1; font-weight: 600; }
2832
2844
  h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
2833
2845
  ol, ul { margin: 0; padding-left: 1.25rem; }
2834
2846
  li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
@@ -2957,7 +2969,8 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
2957
2969
  const tunnelStatus = state.tunnel.up ? s("dashboard.tunnel.up") : s("dashboard.tunnel.down");
2958
2970
  const tunnelClass = state.tunnel.up ? "status-up" : "status-down";
2959
2971
  let attachSection;
2960
- if (qrDataUrl && state.attachUrl) {
2972
+ if (!state.tunnel.up) attachSection = `<p class="hint error">${escapeHtml(s("dashboard.attach.tunnelDown"))}</p>`;
2973
+ else if (qrDataUrl && state.attachUrl) {
2961
2974
  const safeAttachUrl = escapeHtml(state.attachUrl);
2962
2975
  const copyLabel = escapeHtml(s("dashboard.url.copy"));
2963
2976
  attachSection = `<img class="qr" src="${qrDataUrl}" alt="attach QR" /><div class="url-row"><p class="url-box" id="url-box">${safeAttachUrl}</p><button class="copy-btn" id="copy-btn" type="button" aria-label="${copyLabel}">${copyLabel}</button></div>`;
@@ -2974,6 +2987,7 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
2974
2987
  tunnelDown: JSON.stringify(s("dashboard.tunnel.down")),
2975
2988
  pagesEmpty: JSON.stringify(s("dashboard.pages.empty")),
2976
2989
  attachHint: JSON.stringify(s("dashboard.attach.hint")),
2990
+ attachTunnelDown: JSON.stringify(s("dashboard.attach.tunnelDown")),
2977
2991
  copyLabel: JSON.stringify(s("dashboard.url.copy")),
2978
2992
  copiedLabel: JSON.stringify(s("dashboard.url.copied")),
2979
2993
  inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
@@ -3017,6 +3031,7 @@ function buildSseScript(strings) {
3017
3031
  var TUNNEL_DOWN = ${strings.tunnelDown};
3018
3032
  var PAGES_EMPTY = ${strings.pagesEmpty};
3019
3033
  var ATTACH_HINT = ${strings.attachHint};
3034
+ var ATTACH_TUNNEL_DOWN = ${strings.attachTunnelDown};
3020
3035
  var COPY_LABEL = ${strings.copyLabel};
3021
3036
  var COPIED_LABEL = ${strings.copiedLabel};
3022
3037
  var INSPECTOR_OPEN_LABEL = ${strings.inspectorOpenLabel};
@@ -3106,9 +3121,13 @@ function buildSseScript(strings) {
3106
3121
  }
3107
3122
  // attachUrl QR + url-box 갱신
3108
3123
  // SECRET-HANDLING: URL 값을 로그로 출력하지 않는다.
3124
+ // 터널이 죽으면(s.tunnel.up=false) attachUrl이 남아 있어도 QR을
3125
+ // 그리지 않고 에러 상태로 교체한다 — 죽은 QR이 스캔되는 것을 막는다(#631).
3109
3126
  var sec = document.getElementById('attach-section');
3110
3127
  if (sec) {
3111
- if (s.attachUrl) {
3128
+ if (!(s.tunnel && s.tunnel.up)) {
3129
+ sec.innerHTML = '<p class=\\"hint error\\">' + ATTACH_TUNNEL_DOWN + '</p>';
3130
+ } else if (s.attachUrl) {
3112
3131
  var encoded = encodeURIComponent(s.attachUrl);
3113
3132
  var safeUrl = String(s.attachUrl).slice(0, 2000).replace(/[<>&"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; });
3114
3133
  ${isDashboard ? `// dashboard: #attach-section innerHTML 전체 교체 (img + url-row).
@@ -3196,6 +3215,7 @@ function buildAttachHtml(qrDataUrl, safeLabel, safeAttachUrl, locale, path = "/a
3196
3215
  tunnelDown: JSON.stringify(s("dashboard.tunnel.down")),
3197
3216
  pagesEmpty: JSON.stringify(s("dashboard.pages.empty")),
3198
3217
  attachHint: JSON.stringify(s("dashboard.attach.hint")),
3218
+ attachTunnelDown: JSON.stringify(s("dashboard.attach.tunnelDown")),
3199
3219
  copyLabel: JSON.stringify(s("dashboard.url.copy")),
3200
3220
  copiedLabel: JSON.stringify(s("dashboard.url.copied")),
3201
3221
  inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
@@ -3922,7 +3942,7 @@ const DEBUG_TOOL_DEFINITIONS = [
3922
3942
  },
3923
3943
  {
3924
3944
  name: "AIT.getSdkCallHistory",
3925
- description: "Returns the recent Apps In Toss SDK call trace (method, args, result/error, timestamp) that raw CDP cannot observe. Read-only. Use to confirm an SDK call fired and how it resolved (e.g. a saveBase64Data permission regression).",
3945
+ description: "Returns the recent Apps in Toss SDK call trace (method, args, result/error, timestamp) that raw CDP cannot observe. Read-only. Use to confirm an SDK call fired and how it resolved (e.g. a saveBase64Data permission regression).",
3926
3946
  inputSchema: {
3927
3947
  type: "object",
3928
3948
  properties: {},
@@ -4813,7 +4833,7 @@ async function readMcpSdkVersion() {
4813
4833
  * some test environments that skip the build step).
4814
4834
  */
4815
4835
  function readDevtoolsVersion() {
4816
- return "0.1.103";
4836
+ return "0.1.104";
4817
4837
  }
4818
4838
  /**
4819
4839
  * Derives the next recommended action from a completed diagnostics snapshot.
@@ -5405,7 +5425,7 @@ function createDebugServer(deps) {
5405
5425
  const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
5406
5426
  const server = new Server({
5407
5427
  name: "ait-debug",
5408
- version: "0.1.103"
5428
+ version: "0.1.104"
5409
5429
  }, { capabilities: { tools: { listChanged: true } } });
5410
5430
  server.setRequestHandler(ListToolsRequestSchema, () => {
5411
5431
  const conn = router.active;
@@ -6189,6 +6209,7 @@ async function bootRelayFamily(options = {}) {
6189
6209
  onPermanentDrop: (droppedAt) => {
6190
6210
  tunnelStatus = makeTunnelStatus(false, null, droppedAt, 3);
6191
6211
  logError("tunnel.down", { msg: `tunnel permanently dropped (${droppedAt}). Restart: npx @ait-co/devtools devtools-mcp` });
6212
+ options.onTunnelDown?.();
6192
6213
  }
6193
6214
  });
6194
6215
  return printAttachBanner({
@@ -6550,7 +6571,8 @@ async function runDebugServer(options = {}) {
6550
6571
  activeTunnelChildPid = pid;
6551
6572
  lockHandle.updateTunnelChildPid(pid);
6552
6573
  },
6553
- onAuthReject: () => diagnosticsCollector.recordAuthReject()
6574
+ onAuthReject: () => diagnosticsCollector.recordAuthReject(),
6575
+ onTunnelDown: () => qrServer?.notifyStateChange()
6554
6576
  }),
6555
6577
  diagnosticsCollector,
6556
6578
  devtoolsOpener,
@@ -6763,7 +6785,8 @@ async function runLocalDebugServer(options = {}) {
6763
6785
  activeTunnelChildPid = pid;
6764
6786
  lockHandle.updateTunnelChildPid(pid);
6765
6787
  },
6766
- onAuthReject: () => diagnosticsCollector.recordAuthReject()
6788
+ onAuthReject: () => diagnosticsCollector.recordAuthReject(),
6789
+ onTunnelDown: () => qrServer?.notifyStateChange()
6767
6790
  }),
6768
6791
  diagnosticsCollector,
6769
6792
  devtoolsOpener,
@@ -6959,7 +6982,8 @@ async function runMobileDebugServer(options = {}) {
6959
6982
  activeTunnelChildPid = pid;
6960
6983
  lockHandle.updateTunnelChildPid(pid);
6961
6984
  },
6962
- onAuthReject: () => diagnosticsCollector.recordAuthReject()
6985
+ onAuthReject: () => diagnosticsCollector.recordAuthReject(),
6986
+ onTunnelDown: () => qrServer?.notifyStateChange()
6963
6987
  }),
6964
6988
  diagnosticsCollector,
6965
6989
  devtoolsOpener,
@@ -7539,7 +7563,7 @@ function createDevServer(deps = {}) {
7539
7563
  const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
7540
7564
  const server = new Server({
7541
7565
  name: "ait-devtools",
7542
- version: "0.1.103"
7566
+ version: "0.1.104"
7543
7567
  }, { capabilities: { tools: {} } });
7544
7568
  server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
7545
7569
  server.setRequestHandler(CallToolRequestSchema, async (request) => {