@ait-co/devtools 0.1.22 → 0.1.24

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
@@ -13,7 +13,7 @@ Lets you develop and test Apps in Toss mini-apps in a **regular browser** — wi
13
13
  - **60+ SDK API mocks** — auth, payments, IAP, location, camera, storage, and more
14
14
  - **Device API mode system** — switch between mock / web / prompt modes for device APIs
15
15
  - **Device simulation** — iPhone/Galaxy presets + orientation toggle to simulate a mobile viewport in your desktop browser
16
- - **Floating DevTools Panel** — control SDK state in real time from the browser (10 tabs, mock state preset library included)
16
+ - **Floating DevTools Panel** — control SDK state in real time from the browser (12 tabs, mock state preset library included)
17
17
  - **All bundlers supported** — [unplugin](https://github.com/unjs/unplugin)-based Vite, Webpack, Rspack, esbuild, and Rollup integration
18
18
 
19
19
  Live demo: <https://devtools.aitc.dev/> (the `e2e/fixture/` from this repo deployed to GitHub Pages as a self-contained demo).
@@ -170,12 +170,14 @@ module.exports = {
170
170
  | `panel` | `boolean` | `true` | Auto-inject the DevTools Panel |
171
171
  | `forceEnable` | `boolean` | `false` | Enable devtools even in production |
172
172
  | `mock` | `boolean` | `true` (dev) / `false` (prod+forceEnable) | Enable mock alias |
173
+ | `mcp` | `boolean` | `false` | Add an MCP state endpoint to the Vite dev server (Vite only — see [MCP Server](#mcp-server)) |
173
174
  | `tunnel` | `boolean \| { port?: number; qr?: boolean }` | `false` | Expose the Vite dev server via a Cloudflare quick tunnel for real-device preview (see [below](#run-on-a-real-phone)). **Vite dev mode only** |
174
175
 
175
176
  ```ts
176
177
  aitDevtools.vite({ panel: false }); // mock only, no panel
177
178
  aitDevtools.vite({ forceEnable: true }); // enable in production (mock OFF by default, panel ON)
178
179
  aitDevtools.vite({ forceEnable: true, mock: true }); // enable mock in production too
180
+ aitDevtools.vite({ mcp: true }); // enable MCP endpoint for AI agents
179
181
  aitDevtools.vite({ tunnel: true }); // expose dev server at *.trycloudflare.com
180
182
  ```
181
183
 
@@ -336,7 +338,7 @@ Camera and album APIs return dummy images in mock mode.
336
338
 
337
339
  When using the plugin, the panel is auto-injected into your entry point file. Click the **'AIT' button** in the bottom-right corner of the screen to toggle it.
338
340
 
339
- ### 10 tabs
341
+ ### 12 tabs
340
342
 
341
343
  | Tab | Description |
342
344
  |---|---|
@@ -344,9 +346,11 @@ When using the plugin, the panel is auto-injected into your entry point file. Cl
344
346
  | **Presets** | Apply/remove common QA scenarios (permission denied, offline, logged out, etc.) with one click. Save and delete user presets |
345
347
  | **Viewport** | Simulate a mobile viewport using device presets (iPhone/Galaxy) + orientation toggle |
346
348
  | **Permissions** | Control camera, photos, geolocation, clipboard, contacts, and microphone permission states (allowed/denied/notDetermined) |
349
+ | **Notifications** | Choose the next result of the notification-consent flow (new agreement / already agreed / rejected) |
347
350
  | **Location** | Set latitude, longitude, and accuracy |
348
351
  | **Device** | Switch API modes (mock/web/prompt), manage dummy images (add/remove/reset to defaults) |
349
352
  | **IAP** | Choose the next purchase result (success/cancel/error, etc.), TossPay payment result, completed order history (last 5) |
353
+ | **Ads** | Trigger full-screen ad load/show and view the last ad event log |
350
354
  | **Events** | Trigger Back/Home navigation events, toggle login state |
351
355
  | **Analytics** | Real-time log viewer for recorded analytics events (last 30 entries, with timestamp/type/parameters) |
352
356
  | **Storage** | View and clear items stored via the `Storage` API |
@@ -493,7 +497,7 @@ The bottom of the Viewport tab shows the currently applied values in real time:
493
497
  ### Known limitations
494
498
 
495
499
  - **Body becomes the scroll container** — while the viewport is active, scrolling happens on `document.body` rather than `window`. `window.addEventListener('scroll', ...)` or `IntersectionObserver` attached to the root may behave differently from a real device. If your mini-app handles scrolling, verify it against `body` as well.
496
- - **Estimated presets** — iPhone Air is labeled `(est)` (not yet released) and will be updated when it ships. Galaxy S26 series is based on published spec (phone-simulator.com measurements), but safe area values are temporarily from S25 — pixel-accurate QA should be verified on a real device.
500
+ - **Estimated safe area** — Galaxy S26 series is based on published spec (phone-simulator.com measurements), but safe area values are temporarily from S25 — pixel-accurate QA should be verified on a real device.
497
501
 
498
502
  ## `window.__ait` console API
499
503
 
@@ -780,7 +784,7 @@ Please file an issue: https://github.com/apps-in-toss-community/devtools/issues
780
784
  5. Write tests in `src/__tests__/`
781
785
 
782
786
  ```bash
783
- pnpm build # build with tsup
787
+ pnpm build # build with tsdown
784
788
  pnpm typecheck # verify type compatibility
785
789
  pnpm test # run all tests
786
790
  ```
@@ -823,6 +827,109 @@ Since Turbopack doesn't support unplugin, use `resolveAlias` in `next.config.js`
823
827
  import '@ait-co/devtools/panel';
824
828
  ```
825
829
 
830
+ ## MCP Server
831
+
832
+ AI coding agents (Claude Code, Cursor, etc.) can observe a running mini-app directly via [MCP (Model Context Protocol)](https://modelcontextprotocol.io/). A single `devtools-mcp` binary provides two modes.
833
+
834
+ | Mode | Invocation | Target | Tools |
835
+ |---|---|---|---|
836
+ | **debug** (default) | `devtools-mcp` | Production bundle on a phone or dev browser (CDP/Chii) | console/network/page + DOM/snapshot/screenshot + `AIT.*` |
837
+ | **dev** | `devtools-mcp --mode=dev` | Mock state from a running Vite dev server | `AIT.*` (+ `devtools_get_mock_state` alias) |
838
+
839
+ Both modes expose the same `AIT.*` tool surface — debug mode backed by the Chii channel, dev mode by the dev server's mock-state HTTP endpoint — so an agent sees the same tools whether attached to a phone (debug) or a dev browser (dev).
840
+
841
+ ### Debug mode (CDP via Chii)
842
+
843
+ > Read-only tools only. The phone attach roundtrip requires a real-device dog-food step and is deferred to a later phase; the tool layer is CI-verified via mockable injectable CDP connection / AIT source.
844
+
845
+ Running `devtools-mcp` as a stdio server starts a local Chii relay on `:9100` and opens a cloudflared quick tunnel, printing a public `wss://*.trycloudflare.com` URL, a QR code, and a secret token in the terminal. When the phone enters the dogfood entry point, the in-app attach UI connects to the relay with that URL and token, and the agent reads console/network/page state via `chrome-devtools-mcp`-compatible tools — diagnosing regressions without anyone watching the phone.
846
+
847
+ ```json
848
+ {
849
+ "mcpServers": {
850
+ "ait-debug": {
851
+ "command": "pnpm",
852
+ "args": ["exec", "devtools-mcp"]
853
+ }
854
+ }
855
+ }
856
+ ```
857
+
858
+ | Tool | CDP / AIT backing | Description |
859
+ |---|---|---|
860
+ | `list_console_messages` | `Runtime.consoleAPICalled` | Recent console.log/warn/error messages (level, text, timestamp, args) |
861
+ | `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | Recent XHR/fetch requests (url, method, status, timing) |
862
+ | `list_pages` | Chii relay target list | Attached pages + tunnel status + wss URL |
863
+ | `get_dom_document` | `DOM.getDocument` | DOM tree read (structural/layout regression diagnosis) |
864
+ | `take_snapshot` | `DOMSnapshot.captureSnapshot` | Page snapshot (documents + interned strings, visual regression) |
865
+ | `take_screenshot` | `Page.captureScreenshot` | Page PNG screenshot (returned as an MCP image content block) |
866
+ | `AIT.getSdkCallHistory` | AIT domain | SDK call trace (method, args, result/error, timestamp) |
867
+ | `AIT.getMockState` | AIT domain | Mock state snapshot (`window.__ait`) |
868
+ | `AIT.getOperationalEnvironment` | AIT domain | `getOperationalEnvironment()` + SDK version |
869
+
870
+ `AIT.*` covers what raw CDP cannot; the same MCP server forwards it alongside CDP. In debug mode the in-app side answers over the Chii channel (phone integration is a later phase).
871
+
872
+ ### Dev mode (mock state)
873
+
874
+ `devtools-mcp --mode=dev` reads the mock state from a running browser. Phase 3 aligned it on the same `AIT.*` tool surface as debug mode.
875
+
876
+ #### Architecture
877
+
878
+ ```
879
+ Browser (aitState)
880
+ └─ POST /api/ait-devtools/state (auto-pushed by the panel on every state change)
881
+ └─ Vite dev server (unplugin with mcp: true)
882
+ └─ GET /api/ait-devtools/state
883
+ └─ MCP stdio server (dist/mcp/server.js)
884
+ └─ AI agent (AIT.getMockState tool)
885
+ ```
886
+
887
+ #### Setup
888
+
889
+ **1. Add `mcp: true` to the Vite plugin**
890
+
891
+ ```ts
892
+ // vite.config.ts
893
+ import aitDevtools from '@ait-co/devtools/unplugin';
894
+
895
+ export default {
896
+ plugins: [aitDevtools.vite({ mcp: true })],
897
+ };
898
+ ```
899
+
900
+ **2. Configure your MCP client (e.g. Claude Code `.claude/settings.json`)**
901
+
902
+ ```json
903
+ {
904
+ "mcpServers": {
905
+ "ait-devtools": {
906
+ "command": "pnpm",
907
+ "args": ["exec", "devtools-mcp", "--mode=dev"],
908
+ "env": {
909
+ "AIT_DEVTOOLS_URL": "http://localhost:5173"
910
+ }
911
+ }
912
+ }
913
+ }
914
+ ```
915
+
916
+ `AIT_DEVTOOLS_URL` defaults to `http://localhost:5173` — you can omit it if you're using the default port.
917
+
918
+ **3. Open the app in your browser, then call the tool from your AI agent**
919
+
920
+ ```
921
+ > AIT.getMockState
922
+ ```
923
+
924
+ Returns the full current mock state (permissions, location, auth, network, IAP, etc.) as JSON.
925
+
926
+ | Tool | Description |
927
+ |---|---|
928
+ | `AIT.getMockState` | Returns the current `AitDevtoolsState` snapshot (read-only) |
929
+ | `AIT.getOperationalEnvironment` | Environment + version derived from the mock state's `environment` + `appVersion` |
930
+ | `AIT.getSdkCallHistory` | Empty in dev mode (the HTTP endpoint records no trace) |
931
+ | `devtools_get_mock_state` | Backward-compatible alias of `AIT.getMockState` (prefer `AIT.getMockState` in new configs) |
932
+
826
933
  ## Package export structure
827
934
 
828
935
  | Import path | Purpose |
@@ -830,6 +937,9 @@ import '@ait-co/devtools/panel';
830
937
  | `@ait-co/devtools` or `@ait-co/devtools/mock` | All mock exports (bundler alias target) |
831
938
  | `@ait-co/devtools/panel` | Floating DevTools Panel (auto-mounts on import) |
832
939
  | `@ait-co/devtools/unplugin` | Bundler plugin (.vite, .webpack, .rspack, .esbuild, .rollup) |
940
+ | `@ait-co/devtools/mcp/server` | Dev-mode MCP stdio server function (Node.js) |
941
+ | `@ait-co/devtools/mcp/cli` | `devtools-mcp` bin entry point (debug / dev mode, Node.js) |
942
+ | `@ait-co/devtools/in-app` | In-app debug attach — 3-layer gate + Chii target.js injection (dogfood builds only; active when `__DEBUG_BUILD__=true`) |
833
943
 
834
944
  ## Telemetry
835
945
 
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - **60+ SDK API mock** — 인증, 결제, IAP, 위치, 카메라, 스토리지 등
14
14
  - **Device API 모드 시스템** — mock / web / prompt 세 가지 모드로 디바이스 API 동작 전환
15
15
  - **Device simulation** — iPhone/Galaxy 프리셋 + orientation 토글로 데스크탑 브라우저에서 모바일 뷰포트 시뮬레이션
16
- - **Floating DevTools Panel** — 브라우저에서 SDK 상태를 실시간으로 제어 (10개 탭, mock state preset library 포함)
16
+ - **Floating DevTools Panel** — 브라우저에서 SDK 상태를 실시간으로 제어 (12개 탭, mock state preset library 포함)
17
17
  - **모든 번들러 지원** — [unplugin](https://github.com/unjs/unplugin) 기반 Vite, Webpack, Rspack, esbuild, Rollup 통합
18
18
 
19
19
  라이브 데모: <https://devtools.aitc.dev/> (이 repo의 `e2e/fixture/`를 GitHub Pages에 그대로 배포한 self-contained 데모).
@@ -170,12 +170,14 @@ module.exports = {
170
170
  | `panel` | `boolean` | `true` | DevTools Panel 자동 주입 여부 |
171
171
  | `forceEnable` | `boolean` | `false` | production에서도 devtools 활성화 |
172
172
  | `mock` | `boolean` | `true` (dev) / `false` (prod+forceEnable) | mock alias 활성화 여부 |
173
+ | `mcp` | `boolean` | `false` | Vite dev server에 MCP state endpoint 추가 (Vite 전용, [MCP 섹션](#mcp-server) 참조) |
173
174
  | `tunnel` | `boolean \| { port?: number; qr?: boolean }` | `false` | Vite dev 서버를 Cloudflare quick tunnel로 노출 (실기기 미리보기, [아래](#run-on-a-real-phone-실기기-미리보기) 참고). **Vite dev 모드 전용** |
174
175
 
175
176
  ```ts
176
177
  aitDevtools.vite({ panel: false }); // Panel 없이 mock만 사용
177
178
  aitDevtools.vite({ forceEnable: true }); // production에서도 활성화 (mock 기본 OFF, panel ON)
178
179
  aitDevtools.vite({ forceEnable: true, mock: true }); // production에서 mock도 활성화
180
+ aitDevtools.vite({ mcp: true }); // AI 에이전트용 MCP endpoint 활성화
179
181
  aitDevtools.vite({ tunnel: true }); // dev 서버를 *.trycloudflare.com으로 노출
180
182
  ```
181
183
 
@@ -336,7 +338,7 @@ mock 모드에서 카메라/앨범 API는 더미 이미지를 반환합니다.
336
338
 
337
339
  플러그인 사용 시 진입점 파일에 패널이 자동 주입됩니다. 화면 우하단의 **'AIT' 버튼**을 클릭하면 토글됩니다.
338
340
 
339
- ### 10개 탭
341
+ ### 12개 탭
340
342
 
341
343
  | 탭 | 설명 |
342
344
  |---|---|
@@ -344,9 +346,11 @@ mock 모드에서 카메라/앨범 API는 더미 이미지를 반환합니다.
344
346
  | **Presets** | 자주 쓰는 QA 시나리오(권한 거부, offline, 미로그인 등)를 한 클릭으로 적용/해제. 사용자 preset 저장/삭제 가능 |
345
347
  | **Viewport** | 디바이스 프리셋(iPhone/Galaxy) + orientation 토글로 모바일 뷰포트 시뮬레이션 |
346
348
  | **Permissions** | camera, photos, geolocation, clipboard, contacts, microphone 권한 상태 제어 (allowed/denied/notDetermined) |
349
+ | **Notifications** | 알림 동의 흐름의 다음 결과 선택 (신규 동의 / 이미 동의함 / 거부) |
347
350
  | **Location** | 위도, 경도, 정확도 설정 |
348
351
  | **Device** | API 모드 전환 (mock/web/prompt), 더미 이미지 관리 (추가/제거/기본값/초기화) |
349
352
  | **IAP** | 다음 구매 결과 선택 (success/취소/에러 등), TossPay 결제 결과, 완료된 주문 내역 (최근 5건) |
353
+ | **Ads** | 전면 광고 load/show 트리거 및 마지막 광고 이벤트 로그 |
350
354
  | **Events** | Back/Home 네비게이션 이벤트 트리거, 로그인 상태 토글 |
351
355
  | **Analytics** | 기록된 분석 이벤트 실시간 로그 뷰어 (최근 30건, 타임스탬프/타입/파라미터) |
352
356
  | **Storage** | `Storage` API로 저장된 항목 조회 및 초기화 |
@@ -493,7 +497,7 @@ Viewport 탭 하단에 현재 적용된 값을 실시간으로 보여줍니다:
493
497
  ### Known limitations
494
498
 
495
499
  - **Body가 스크롤 컨테이너가 됩니다** — 뷰포트 활성화 중에는 스크롤이 `window`가 아닌 `document.body`에서 발생합니다. `window.addEventListener('scroll', ...)`나 root에 붙은 `IntersectionObserver`는 실 디바이스와 다른 동작을 보일 수 있습니다. 미니앱 코드에서 스크롤을 다룬다면 `body`도 함께 검증하세요.
496
- - **추정 프리셋**iPhone Air는 `(est)` 라벨(미출시)로 표시되며, 실제 출시 후 갱신 예정입니다. Galaxy S26 시리즈는 출시 spec(phone-simulator.com 측정치) 기반이지만 safe area는 S25 값을 잠정 사용 — 픽셀 단위 정확도가 필요한 QA는 실 기기 확인을 권장합니다.
500
+ - **추정 safe area** — Galaxy S26 시리즈는 출시 spec(phone-simulator.com 측정치) 기반이지만 safe area는 S25 값을 잠정 사용합니다 — 픽셀 단위 정확도가 필요한 QA는 실 기기 확인을 권장합니다.
497
501
 
498
502
  ## `window.__ait` 콘솔 API
499
503
 
@@ -780,7 +784,7 @@ Please file an issue: https://github.com/apps-in-toss-community/devtools/issues
780
784
  5. `src/__tests/`에 테스트 작성
781
785
 
782
786
  ```bash
783
- pnpm build # tsup으로 빌드
787
+ pnpm build # tsdown으로 빌드
784
788
  pnpm typecheck # 타입 호환성 검증
785
789
  pnpm test # 전체 테스트 실행
786
790
  ```
@@ -823,6 +827,119 @@ Turbopack은 unplugin을 지원하지 않으므로, `next.config.js`에서 `reso
823
827
  import '@ait-co/devtools/panel';
824
828
  ```
825
829
 
830
+ ## MCP Server
831
+
832
+ AI 코딩 에이전트(Claude Code, Cursor 등)가 [MCP(Model Context Protocol)](https://modelcontextprotocol.io/)를
833
+ 통해 실행 중인 미니앱을 직접 관측할 수 있습니다. 단일 `devtools-mcp` bin이 두 모드를 제공합니다.
834
+
835
+ | 모드 | 호출 | 대상 | tool |
836
+ |---|---|---|---|
837
+ | **debug** (기본값) | `devtools-mcp` | 폰 안 production 번들 또는 dev 브라우저 (CDP/Chii) | console/network/page + DOM/snapshot/screenshot + `AIT.*` |
838
+ | **dev** | `devtools-mcp --mode=dev` | 실행 중인 Vite dev server의 mock state | `AIT.*` (+ `devtools_get_mock_state` alias) |
839
+
840
+ 두 모드는 같은 `AIT.*` tool surface를 노출합니다 — debug 모드는 Chii 채널로, dev 모드는 dev server의
841
+ mock-state HTTP endpoint로 백킹되어, 에이전트가 폰(debug)에 붙든 dev 브라우저(dev)에 붙든 동일한
842
+ tool을 봅니다.
843
+
844
+ ### Debug 모드 (CDP via Chii)
845
+
846
+ > read-only tool만 노출합니다. 폰 attach 라운드트립은 실기기 dog-food가 필요해 후속 phase로 분리되어
847
+ > 있고, tool 계층은 주입 가능한 CDP 연결 / AIT 소스를 mock해 CI에서 검증됩니다.
848
+
849
+ `devtools-mcp`를 stdio로 실행하면 로컬 Chii 릴레이(:9100)를 띄우고 cloudflared quick tunnel로
850
+ 공개 `wss://*.trycloudflare.com` URL을 발급한 뒤 QR + secret token을 터미널에 출력합니다.
851
+ 폰이 dogfood 진입 시 in-app attach UI가 그 URL + token으로 릴레이에 붙으면, 에이전트가
852
+ `chrome-devtools-mcp` 호환 tool로 console/network/page 상태를 read합니다. 사람이 폰을 지켜볼
853
+ 필요 없이 회귀를 단독 진단하는 것이 목표입니다.
854
+
855
+ ```json
856
+ {
857
+ "mcpServers": {
858
+ "ait-debug": {
859
+ "command": "pnpm",
860
+ "args": ["exec", "devtools-mcp"]
861
+ }
862
+ }
863
+ }
864
+ ```
865
+
866
+ | Tool | CDP / AIT 백킹 | 설명 |
867
+ |---|---|---|
868
+ | `list_console_messages` | `Runtime.consoleAPICalled` | 최근 console.log/warn/error 메시지 (level, text, timestamp, args) |
869
+ | `list_network_requests` | `Network.requestWillBeSent` + `responseReceived` | 최근 XHR/fetch 요청 (url, method, status, timing) |
870
+ | `list_pages` | Chii 릴레이 target 목록 | attach된 페이지 + tunnel 상태 + wss URL |
871
+ | `get_dom_document` | `DOM.getDocument` | DOM 트리 read (구조/레이아웃 회귀 진단) |
872
+ | `take_snapshot` | `DOMSnapshot.captureSnapshot` | 페이지 스냅샷 (documents + interned strings, 시각 회귀 진단) |
873
+ | `take_screenshot` | `Page.captureScreenshot` | 페이지 PNG 스크린샷 (MCP image content block 반환) |
874
+ | `AIT.getSdkCallHistory` | AIT 도메인 | SDK 호출 trace (method, args, result/error, timestamp) |
875
+ | `AIT.getMockState` | AIT 도메인 | mock state 스냅샷 (`window.__ait`) |
876
+ | `AIT.getOperationalEnvironment` | AIT 도메인 | `getOperationalEnvironment()` + SDK 버전 |
877
+
878
+ `AIT.*`는 raw CDP가 못 잡는 영역으로, 같은 MCP server가 CDP와 함께 forward합니다. debug 모드에서는
879
+ in-app 측이 Chii 채널로 응답합니다(폰 통합은 후속 phase).
880
+
881
+ ### Dev 모드 (mock state)
882
+
883
+ `devtools-mcp --mode=dev`는 실행 중인 브라우저의 mock state를 읽습니다. Phase 3에서 debug 모드와
884
+ 같은 `AIT.*` tool surface로 정렬되었습니다.
885
+
886
+ #### 구조
887
+
888
+ ```
889
+ 브라우저 (aitState)
890
+ └─ POST /api/ait-devtools/state (panel이 state 변경 시 자동 push)
891
+ └─ Vite dev server (unplugin mcp: true 로 등록)
892
+ └─ GET /api/ait-devtools/state
893
+ └─ MCP stdio server (dist/mcp/server.js)
894
+ └─ AI 에이전트 (AIT.getMockState tool)
895
+ ```
896
+
897
+ #### 설정
898
+
899
+ **1. Vite 플러그인에 `mcp: true` 추가**
900
+
901
+ ```ts
902
+ // vite.config.ts
903
+ import aitDevtools from '@ait-co/devtools/unplugin';
904
+
905
+ export default {
906
+ plugins: [aitDevtools.vite({ mcp: true })],
907
+ };
908
+ ```
909
+
910
+ **2. MCP 클라이언트 설정 (예: Claude Code `.claude/settings.json`)**
911
+
912
+ ```json
913
+ {
914
+ "mcpServers": {
915
+ "ait-devtools": {
916
+ "command": "pnpm",
917
+ "args": ["exec", "devtools-mcp", "--mode=dev"],
918
+ "env": {
919
+ "AIT_DEVTOOLS_URL": "http://localhost:5173"
920
+ }
921
+ }
922
+ }
923
+ }
924
+ ```
925
+
926
+ `AIT_DEVTOOLS_URL`은 기본값이 `http://localhost:5173`이므로 기본 포트를 쓰면 생략 가능합니다.
927
+
928
+ **3. 앱을 브라우저에서 열고, AI 에이전트에서 tool 호출**
929
+
930
+ ```
931
+ > AIT.getMockState
932
+ ```
933
+
934
+ 현재 mock state 전체(권한, 위치, 인증, 네트워크, IAP 등)를 JSON으로 반환합니다.
935
+
936
+ | Tool | 설명 |
937
+ |---|---|
938
+ | `AIT.getMockState` | 현재 `AitDevtoolsState` 스냅샷 반환 (read-only) |
939
+ | `AIT.getOperationalEnvironment` | mock state의 `environment` + `appVersion` 기반 환경/버전 |
940
+ | `AIT.getSdkCallHistory` | dev 모드에서는 빈 목록 (HTTP endpoint가 trace를 기록하지 않음) |
941
+ | `devtools_get_mock_state` | `AIT.getMockState`의 하위호환 alias (신규 설정은 `AIT.getMockState` 권장) |
942
+
826
943
  ## 패키지 Export 구조
827
944
 
828
945
  | Import path | 용도 |
@@ -830,6 +947,9 @@ import '@ait-co/devtools/panel';
830
947
  | `@ait-co/devtools` 또는 `@ait-co/devtools/mock` | 모든 mock export (번들러 alias 대상) |
831
948
  | `@ait-co/devtools/panel` | Floating DevTools Panel (import 시 자동 마운트) |
832
949
  | `@ait-co/devtools/unplugin` | 번들러 플러그인 (.vite, .webpack, .rspack, .esbuild, .rollup) |
950
+ | `@ait-co/devtools/mcp/server` | dev-mode MCP stdio server 함수 (Node.js) |
951
+ | `@ait-co/devtools/mcp/cli` | `devtools-mcp` bin 진입점 (debug / dev 모드, Node.js) |
952
+ | `@ait-co/devtools/in-app` | In-app debug attach — 3-layer gate + Chii target.js 주입 (dogfood 빌드 전용, `__DEBUG_BUILD__=true` 시에만 활성) |
833
953
 
834
954
  ## 텔레메트리
835
955
 
@@ -0,0 +1,136 @@
1
+ //#region src/in-app/gate.d.ts
2
+ /**
3
+ * 3-layer activation gate for the in-app debug surface.
4
+ *
5
+ * Spec: docs/superpowers/specs/2026-05-18-in-app-debug-mcp.md
6
+ * "3-layer activation gate". This is the pure gate decision; the Chii client,
7
+ * WebSocket transport, MCP server, and CLI that consume it live in src/mcp/.
8
+ *
9
+ * Decision matrix:
10
+ *
11
+ * build channel | _deploymentId | debug=1 | result
12
+ * release | (any) | (any) | BLOCKED (Layer A — code absent via DCE)
13
+ * dogfood | absent | (any) | BLOCKED (Layer B — entry gate)
14
+ * dogfood | present | absent | BLOCKED (Layer C — opt-in gate)
15
+ * dogfood | present | present | ATTACH
16
+ */
17
+ /** Shape returned when the gate allows attachment. */
18
+ interface GateResultAttach {
19
+ readonly attach: true;
20
+ /** The validated `wss:` relay URL from the `relay` query param. */
21
+ readonly relayUrl: string;
22
+ /** The deployment ID extracted from the `_deploymentId` query param. */
23
+ readonly deploymentId: string;
24
+ }
25
+ /** Shape returned when the gate blocks attachment, with a reason code. */
26
+ interface GateResultBlocked {
27
+ readonly attach: false;
28
+ /**
29
+ * - `'build'` Layer A: `__DEBUG_BUILD__` is false (release build).
30
+ * - `'entry'` Layer B: `_deploymentId` param is absent or empty.
31
+ * - `'opt-in'` Layer C: `debug=1` param is absent.
32
+ * - `'invalid-relay'` Layer C: `relay` param is absent, empty, or not a `wss:` URL.
33
+ */
34
+ readonly reason: 'build' | 'entry' | 'opt-in' | 'invalid-relay';
35
+ }
36
+ type GateResult = GateResultAttach | GateResultBlocked;
37
+ /**
38
+ * Input for {@link evaluateDebugGate}.
39
+ *
40
+ * Keeping each field explicit makes the function trivially testable without
41
+ * needing to manipulate `window.location`.
42
+ */
43
+ interface GateInput {
44
+ /**
45
+ * Whether this is a debug build. Corresponds to the `__DEBUG_BUILD__`
46
+ * compile-time constant injected by tsdown.
47
+ *
48
+ * In source code consumed via `@ait-co/devtools/in-app`, the thin
49
+ * `src/in-app/index.ts` entry reads `__DEBUG_BUILD__` and passes it here.
50
+ * Tests supply it directly.
51
+ */
52
+ readonly isDebugBuild: boolean;
53
+ /**
54
+ * The URL search params to inspect for gate signals.
55
+ *
56
+ * Prefer `URLSearchParams` so callers can pass `new URLSearchParams(location.search)`
57
+ * without coupling the pure function to `window`.
58
+ *
59
+ * Layer B open seam (spec open question 2): if the Toss SDK ever exposes
60
+ * `getEntryScheme()` or a similar API that reliably signals a dogfood entry,
61
+ * that signal should be checked before `_deploymentId` here. For now only the
62
+ * `_deploymentId` query param fallback is implemented. Pass a custom
63
+ * `URLSearchParams` to inject the SDK signal at the call site without
64
+ * modifying this function.
65
+ */
66
+ readonly searchParams: URLSearchParams;
67
+ }
68
+ /**
69
+ * Pure function that evaluates the 3-layer debug activation gate.
70
+ *
71
+ * Has no side effects. All inputs are explicit. Returns a discriminated union
72
+ * so callers can pattern-match on `result.attach`.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const result = evaluateDebugGate({
77
+ * isDebugBuild: __DEBUG_BUILD__,
78
+ * searchParams: new URLSearchParams(window.location.search),
79
+ * });
80
+ * if (result.attach) {
81
+ * // Proceed to load Chii client
82
+ * }
83
+ * ```
84
+ */
85
+ declare function evaluateDebugGate(input: GateInput): GateResult;
86
+ //#endregion
87
+ //#region src/in-app/attach.d.ts
88
+ /**
89
+ * Converts a validated `wss:` relay URL into the Chii `target.js` script URL.
90
+ *
91
+ * Scheme is mapped `wss:` → `https:`. Host and port are preserved.
92
+ * Pathname is set to `/target.js` regardless of the relay path.
93
+ * Query params and hash from the relay URL are dropped — the target script
94
+ * URL is a static asset path on the same host.
95
+ *
96
+ * @example
97
+ * deriveTargetScriptUrl('wss://abc.trycloudflare.com/relay')
98
+ * // → 'https://abc.trycloudflare.com/target.js'
99
+ *
100
+ * deriveTargetScriptUrl('wss://h.example.com:9100/')
101
+ * // → 'https://h.example.com:9100/target.js'
102
+ */
103
+ declare function deriveTargetScriptUrl(relayUrl: string): string;
104
+ /**
105
+ * Evaluates the 3-layer debug gate and, if the gate passes, injects the Chii
106
+ * `target.js` script into `document.head`.
107
+ *
108
+ * Idempotent — calling more than once is safe. The second call is a no-op if
109
+ * a script with the same `src` is already present in the document, and the
110
+ * module-level `attached` flag prevents redundant DOM queries after the first
111
+ * successful injection.
112
+ *
113
+ * Safe to call even if `document` is somehow unavailable (defensive boundary
114
+ * guard — in practice this always runs in a real WebView).
115
+ *
116
+ * @param gateResult - Optional pre-evaluated gate result for testability.
117
+ * Defaults to `checkDebugGate()` which reads the current page URL and the
118
+ * `__DEBUG_BUILD__` compile-time constant. Passing a custom value avoids
119
+ * the need to manipulate `window.location` in tests.
120
+ */
121
+ declare function maybeAttach(gateResult?: GateResult): void;
122
+ //#endregion
123
+ //#region src/in-app/index.d.ts
124
+ /**
125
+ * Evaluates the 3-layer debug activation gate against the current page URL.
126
+ *
127
+ * Returns the gate result. Callers can check `result.attach` to decide whether
128
+ * to proceed with debug surface attachment.
129
+ *
130
+ * This function reads `window.location` and the `__DEBUG_BUILD__` compile-time
131
+ * constant. It has no other side effects.
132
+ */
133
+ declare function checkDebugGate(): GateResult;
134
+ //#endregion
135
+ export { type GateInput, type GateResult, type GateResultAttach, type GateResultBlocked, checkDebugGate, deriveTargetScriptUrl, evaluateDebugGate, maybeAttach };
136
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/in-app/gate.ts","../../src/in-app/attach.ts","../../src/in-app/index.ts"],"mappings":";;AAiBA;;;;;;;;;AASA;;;;;AAWA;AAAA,UApBiB,gBAAA;EAAA,SACN,MAAA;EAmBc;EAAA,SAjBd,QAAA;EAyBM;EAAA,SAvBN,YAAA;AAAA;;UAIM,iBAAA;EAAA,SACN,MAAA;EA0Cc;;;AAoBzB;;;EApByB,SAnCd,MAAA;AAAA;AAAA,KAGC,UAAA,GAAa,gBAAA,GAAmB,iBAAA;;;;;;;UAQ3B,SAAA;EChBoB;;;;AA6BrC;;;;EA7BqC,SDyB1B,YAAA;;;;AEzBX;;;;;;;;;;WFwCW,YAAA,EAAc,eAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBT,iBAAA,CAAkB,KAAA,EAAO,SAAA,GAAY,UAAA;;;;;;AApDrD;;;;;AAQA;;;;;;;iBChBgB,qBAAA,CAAsB,QAAA;;AD4DtC;;;;;;;;;;;;AC5DA;;;;iBA6BgB,WAAA,CAAY,UAAA,GAAY,UAAA;;;;;;ADbxC;;;;;;iBEhBgB,cAAA,CAAA,GAAkB,UAAA"}
@@ -0,0 +1,163 @@
1
+ //#region src/in-app/gate.ts
2
+ /**
3
+ * Pure function that evaluates the 3-layer debug activation gate.
4
+ *
5
+ * Has no side effects. All inputs are explicit. Returns a discriminated union
6
+ * so callers can pattern-match on `result.attach`.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const result = evaluateDebugGate({
11
+ * isDebugBuild: __DEBUG_BUILD__,
12
+ * searchParams: new URLSearchParams(window.location.search),
13
+ * });
14
+ * if (result.attach) {
15
+ * // Proceed to load Chii client
16
+ * }
17
+ * ```
18
+ */
19
+ function evaluateDebugGate(input) {
20
+ if (!input.isDebugBuild) return {
21
+ attach: false,
22
+ reason: "build"
23
+ };
24
+ const deploymentId = input.searchParams.get("_deploymentId") ?? "";
25
+ if (deploymentId === "") return {
26
+ attach: false,
27
+ reason: "entry"
28
+ };
29
+ if (input.searchParams.get("debug") !== "1") return {
30
+ attach: false,
31
+ reason: "opt-in"
32
+ };
33
+ const relayRaw = input.searchParams.get("relay") ?? "";
34
+ if (relayRaw === "") return {
35
+ attach: false,
36
+ reason: "invalid-relay"
37
+ };
38
+ let relayUrl;
39
+ try {
40
+ relayUrl = new URL(relayRaw);
41
+ } catch {
42
+ return {
43
+ attach: false,
44
+ reason: "invalid-relay"
45
+ };
46
+ }
47
+ if (relayUrl.protocol !== "wss:") return {
48
+ attach: false,
49
+ reason: "invalid-relay"
50
+ };
51
+ return {
52
+ attach: true,
53
+ relayUrl: relayUrl.href,
54
+ deploymentId
55
+ };
56
+ }
57
+ //#endregion
58
+ //#region src/in-app/attach.ts
59
+ /**
60
+ * In-app Chii target injection for the debug attach flow.
61
+ *
62
+ * Spec: docs/superpowers/specs/2026-05-18-in-app-debug-mcp.md
63
+ * "MCP attach" topology section — Phase 1 browser-side implementation.
64
+ *
65
+ * This module bridges the 3-layer gate result to a Chii `target.js` script
66
+ * injection. The Chii npm package is the relay SERVER — the in-app side is
67
+ * a plain `<script src="…/target.js">` pointing at the relay host. No chii
68
+ * npm dependency is needed here.
69
+ */
70
+ /**
71
+ * Converts a validated `wss:` relay URL into the Chii `target.js` script URL.
72
+ *
73
+ * Scheme is mapped `wss:` → `https:`. Host and port are preserved.
74
+ * Pathname is set to `/target.js` regardless of the relay path.
75
+ * Query params and hash from the relay URL are dropped — the target script
76
+ * URL is a static asset path on the same host.
77
+ *
78
+ * @example
79
+ * deriveTargetScriptUrl('wss://abc.trycloudflare.com/relay')
80
+ * // → 'https://abc.trycloudflare.com/target.js'
81
+ *
82
+ * deriveTargetScriptUrl('wss://h.example.com:9100/')
83
+ * // → 'https://h.example.com:9100/target.js'
84
+ */
85
+ function deriveTargetScriptUrl(relayUrl) {
86
+ const u = new URL(relayUrl);
87
+ u.protocol = "https:";
88
+ u.pathname = "/target.js";
89
+ u.search = "";
90
+ u.hash = "";
91
+ return u.toString();
92
+ }
93
+ /** Module-level guard against double-injection within a page lifecycle. */
94
+ let attached = false;
95
+ /**
96
+ * Evaluates the 3-layer debug gate and, if the gate passes, injects the Chii
97
+ * `target.js` script into `document.head`.
98
+ *
99
+ * Idempotent — calling more than once is safe. The second call is a no-op if
100
+ * a script with the same `src` is already present in the document, and the
101
+ * module-level `attached` flag prevents redundant DOM queries after the first
102
+ * successful injection.
103
+ *
104
+ * Safe to call even if `document` is somehow unavailable (defensive boundary
105
+ * guard — in practice this always runs in a real WebView).
106
+ *
107
+ * @param gateResult - Optional pre-evaluated gate result for testability.
108
+ * Defaults to `checkDebugGate()` which reads the current page URL and the
109
+ * `__DEBUG_BUILD__` compile-time constant. Passing a custom value avoids
110
+ * the need to manipulate `window.location` in tests.
111
+ */
112
+ function maybeAttach(gateResult = checkDebugGate()) {
113
+ if (!gateResult.attach) {
114
+ console.debug(`[@ait-co/devtools] debug attach skipped — gate blocked (reason: ${gateResult.reason})`);
115
+ return;
116
+ }
117
+ if (attached) return;
118
+ if (typeof document === "undefined") return;
119
+ const src = deriveTargetScriptUrl(gateResult.relayUrl);
120
+ if (document.querySelector(`script[src="${src}"]`) !== null) {
121
+ attached = true;
122
+ return;
123
+ }
124
+ const script = document.createElement("script");
125
+ script.src = src;
126
+ script.async = true;
127
+ (document.head ?? document.documentElement).appendChild(script);
128
+ attached = true;
129
+ }
130
+ //#endregion
131
+ //#region src/in-app/index.ts
132
+ /**
133
+ * @ait-co/devtools/in-app entry point.
134
+ *
135
+ * Spec: docs/superpowers/specs/2026-05-18-in-app-debug-mcp.md
136
+ *
137
+ * Phase 1 — gate + browser-side Chii target injection.
138
+ * WebSocket relay, QR/paste UI, and AI-host MCP bin are later phases that
139
+ * require real-device validation and are not included here.
140
+ *
141
+ * This thin entry reads `__DEBUG_BUILD__` and `window.location`, then calls
142
+ * the pure {@link evaluateDebugGate} function. All testable logic lives in
143
+ * `./gate.ts` and `./attach.ts`, not here.
144
+ */
145
+ /**
146
+ * Evaluates the 3-layer debug activation gate against the current page URL.
147
+ *
148
+ * Returns the gate result. Callers can check `result.attach` to decide whether
149
+ * to proceed with debug surface attachment.
150
+ *
151
+ * This function reads `window.location` and the `__DEBUG_BUILD__` compile-time
152
+ * constant. It has no other side effects.
153
+ */
154
+ function checkDebugGate() {
155
+ return evaluateDebugGate({
156
+ isDebugBuild: false,
157
+ searchParams: new URLSearchParams(window.location.search)
158
+ });
159
+ }
160
+ //#endregion
161
+ export { checkDebugGate, deriveTargetScriptUrl, evaluateDebugGate, maybeAttach };
162
+
163
+ //# sourceMappingURL=index.js.map