@ait-co/devtools 0.1.103 → 0.1.105
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 +10 -10
- package/README.md +10 -10
- package/dist/mcp/cli.js +56 -10
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/panel/index.js +3 -1
- package/dist/panel/index.js.map +1 -1
- package/dist/{qr-http-server-C9YPBo6H.cjs → qr-http-server-A9vld8r7.cjs} +23 -3
- package/dist/qr-http-server-A9vld8r7.cjs.map +1 -0
- package/dist/{qr-http-server-Ck8o4PLI.js → qr-http-server-D4EAA7Il.js} +23 -3
- package/dist/qr-http-server-D4EAA7Il.js.map +1 -0
- package/dist/{qr-http-server-DegdwsSj.cjs → qr-http-server-Dj3Z0NHi.cjs} +23 -3
- package/dist/qr-http-server-Dj3Z0NHi.cjs.map +1 -0
- package/dist/{qr-http-server-D09oMVit.js → qr-http-server-HzdCLU8s.js} +23 -3
- package/dist/qr-http-server-HzdCLU8s.js.map +1 -0
- package/dist/{tunnel-qB2Soaaz.js → tunnel-BmDcTrnU.js} +2 -2
- package/dist/{tunnel-qB2Soaaz.js.map → tunnel-BmDcTrnU.js.map} +1 -1
- package/dist/{tunnel-3RCjGaND.cjs → tunnel-RB5zB8IK.cjs} +2 -2
- package/dist/{tunnel-3RCjGaND.cjs.map → tunnel-RB5zB8IK.cjs.map} +1 -1
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/tunnel.cjs +1 -1
- package/dist/unplugin/tunnel.js +1 -1
- package/package.json +1 -1
- package/dist/qr-http-server-C9YPBo6H.cjs.map +0 -1
- package/dist/qr-http-server-Ck8o4PLI.js.map +0 -1
- package/dist/qr-http-server-D09oMVit.js.map +0 -1
- package/dist/qr-http-server-DegdwsSj.cjs.map +0 -1
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@ait-co/devtools) [](./LICENSE)
|
|
6
6
|
|
|
7
|
-

|
|
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
|
|
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
|
|
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 <
|
|
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'})` | — |
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 —
|
|
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
|
[](https://www.npmjs.com/package/@ait-co/devtools) [](./LICENSE)
|
|
6
6
|
|
|
7
|
-

|
|
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에서
|
|
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 인증이 필요한
|
|
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 <
|
|
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'})` | — | 폰 안
|
|
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 디버깅 루프(
|
|
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
|
-
코드는 출력하지 않습니다). 폰이
|
|
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 (
|
|
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` 경유) |
|
|
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 —
|
|
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 (
|
|
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.
|
|
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")),
|
|
@@ -3540,6 +3560,24 @@ function killAndWait(pid, graceMs = 2e3) {
|
|
|
3540
3560
|
} catch {}
|
|
3541
3561
|
}
|
|
3542
3562
|
/**
|
|
3563
|
+
* Reaps an orphaned cloudflared tunnel child left behind by a previous session
|
|
3564
|
+
* (issue #628). The normal shutdown path TERM-cascades to the child, but a
|
|
3565
|
+
* SIGKILL'd or crashed Node process can't run cleanup — its cloudflared child
|
|
3566
|
+
* keeps the (now stale) quick tunnel alive. When `acquireLock` reclaims such a
|
|
3567
|
+
* lock, this kills the still-alive `tunnelChildPid` so the new session starts
|
|
3568
|
+
* from a clean slate (companion to the #347/#571 zombie-daemon defenses).
|
|
3569
|
+
*
|
|
3570
|
+
* No-op when the lock carries no `tunnelChildPid` (older lock files) or the
|
|
3571
|
+
* child is already gone. SECRET-HANDLING: logs the PID only — never the tunnel
|
|
3572
|
+
* host/wss (those never enter the lock file or this path).
|
|
3573
|
+
*/
|
|
3574
|
+
function reapOrphanTunnelChild(existing) {
|
|
3575
|
+
const childPid = existing.tunnelChildPid;
|
|
3576
|
+
if (typeof childPid !== "number" || !isPidAlive(childPid)) return;
|
|
3577
|
+
process.stderr.write(`[ait-debug] reaping orphaned tunnel child PID=${childPid} from previous session.\n`);
|
|
3578
|
+
killAndWait(childPid);
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3543
3581
|
* Reads the current lock file without acquiring it. Returns the parsed
|
|
3544
3582
|
* `LockData` when the file exists and is valid, otherwise `null`. Used by
|
|
3545
3583
|
* `get_debug_status` to surface the `serverLockHolder` field without
|
|
@@ -3571,13 +3609,17 @@ function acquireLock(options = {}) {
|
|
|
3571
3609
|
else if (force) {
|
|
3572
3610
|
process.stderr.write(`[ait-debug] --force: terminating existing session PID=${existing.pid} …\n`);
|
|
3573
3611
|
killAndWait(existing.pid);
|
|
3612
|
+
reapOrphanTunnelChild(existing);
|
|
3574
3613
|
process.stderr.write(`[ait-debug] --force: PID=${existing.pid} stopped, taking over.\n`);
|
|
3575
3614
|
} else {
|
|
3576
3615
|
const urlPart = existing.wssUrl != null ? `wssUrl=${existing.wssUrl}` : "wssUrl=(tunnel starting)";
|
|
3577
3616
|
process.stderr.write(`[ait-debug] 기존 debug-mode 세션이 이미 실행 중 — PID=${existing.pid}, started ${existing.startedAt}, ${urlPart}\n[ait-debug] 회복: \`kill ${existing.pid}\` 또는 \`npx @ait-co/devtools devtools-mcp --force\`\n`);
|
|
3578
3617
|
throw new ServerLockConflictError(existing.pid, existing.wssUrl, existing.startedAt);
|
|
3579
3618
|
}
|
|
3580
|
-
} else
|
|
3619
|
+
} else {
|
|
3620
|
+
process.stderr.write(`[ait-debug] stale lock from PID ${existing.pid} recovered — starting fresh.\n`);
|
|
3621
|
+
reapOrphanTunnelChild(existing);
|
|
3622
|
+
}
|
|
3581
3623
|
const data = {
|
|
3582
3624
|
pid: process.pid,
|
|
3583
3625
|
wssUrl: null,
|
|
@@ -3922,7 +3964,7 @@ const DEBUG_TOOL_DEFINITIONS = [
|
|
|
3922
3964
|
},
|
|
3923
3965
|
{
|
|
3924
3966
|
name: "AIT.getSdkCallHistory",
|
|
3925
|
-
description: "Returns the recent Apps
|
|
3967
|
+
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
3968
|
inputSchema: {
|
|
3927
3969
|
type: "object",
|
|
3928
3970
|
properties: {},
|
|
@@ -4813,7 +4855,7 @@ async function readMcpSdkVersion() {
|
|
|
4813
4855
|
* some test environments that skip the build step).
|
|
4814
4856
|
*/
|
|
4815
4857
|
function readDevtoolsVersion() {
|
|
4816
|
-
return "0.1.
|
|
4858
|
+
return "0.1.105";
|
|
4817
4859
|
}
|
|
4818
4860
|
/**
|
|
4819
4861
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -5405,7 +5447,7 @@ function createDebugServer(deps) {
|
|
|
5405
5447
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
5406
5448
|
const server = new Server({
|
|
5407
5449
|
name: "ait-debug",
|
|
5408
|
-
version: "0.1.
|
|
5450
|
+
version: "0.1.105"
|
|
5409
5451
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
5410
5452
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
5411
5453
|
const conn = router.active;
|
|
@@ -6189,6 +6231,7 @@ async function bootRelayFamily(options = {}) {
|
|
|
6189
6231
|
onPermanentDrop: (droppedAt) => {
|
|
6190
6232
|
tunnelStatus = makeTunnelStatus(false, null, droppedAt, 3);
|
|
6191
6233
|
logError("tunnel.down", { msg: `tunnel permanently dropped (${droppedAt}). Restart: npx @ait-co/devtools devtools-mcp` });
|
|
6234
|
+
options.onTunnelDown?.();
|
|
6192
6235
|
}
|
|
6193
6236
|
});
|
|
6194
6237
|
return printAttachBanner({
|
|
@@ -6550,7 +6593,8 @@ async function runDebugServer(options = {}) {
|
|
|
6550
6593
|
activeTunnelChildPid = pid;
|
|
6551
6594
|
lockHandle.updateTunnelChildPid(pid);
|
|
6552
6595
|
},
|
|
6553
|
-
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6596
|
+
onAuthReject: () => diagnosticsCollector.recordAuthReject(),
|
|
6597
|
+
onTunnelDown: () => qrServer?.notifyStateChange()
|
|
6554
6598
|
}),
|
|
6555
6599
|
diagnosticsCollector,
|
|
6556
6600
|
devtoolsOpener,
|
|
@@ -6763,7 +6807,8 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6763
6807
|
activeTunnelChildPid = pid;
|
|
6764
6808
|
lockHandle.updateTunnelChildPid(pid);
|
|
6765
6809
|
},
|
|
6766
|
-
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6810
|
+
onAuthReject: () => diagnosticsCollector.recordAuthReject(),
|
|
6811
|
+
onTunnelDown: () => qrServer?.notifyStateChange()
|
|
6767
6812
|
}),
|
|
6768
6813
|
diagnosticsCollector,
|
|
6769
6814
|
devtoolsOpener,
|
|
@@ -6959,7 +7004,8 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6959
7004
|
activeTunnelChildPid = pid;
|
|
6960
7005
|
lockHandle.updateTunnelChildPid(pid);
|
|
6961
7006
|
},
|
|
6962
|
-
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
7007
|
+
onAuthReject: () => diagnosticsCollector.recordAuthReject(),
|
|
7008
|
+
onTunnelDown: () => qrServer?.notifyStateChange()
|
|
6963
7009
|
}),
|
|
6964
7010
|
diagnosticsCollector,
|
|
6965
7011
|
devtoolsOpener,
|
|
@@ -7539,7 +7585,7 @@ function createDevServer(deps = {}) {
|
|
|
7539
7585
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
7540
7586
|
const server = new Server({
|
|
7541
7587
|
name: "ait-devtools",
|
|
7542
|
-
version: "0.1.
|
|
7588
|
+
version: "0.1.105"
|
|
7543
7589
|
}, { capabilities: { tools: {} } });
|
|
7544
7590
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
7545
7591
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|