@ait-co/devtools 0.1.73 → 0.1.75
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/dist/devtools-opener-BbUXBzgA.js.map +1 -1
- package/dist/devtools-opener-Bp671YXu.cjs.map +1 -1
- package/dist/devtools-opener-D84kZFtR.js.map +1 -1
- package/dist/devtools-opener-h6A-UjzC.cjs.map +1 -1
- package/dist/mcp/cli.js +191 -76
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mock/index.d.ts +50 -2
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +1210 -1110
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +828 -820
- package/dist/panel/index.js.map +1 -1
- package/dist/{qr-http-server-Ditd2ndz.js → qr-http-server-CDO6o2nr.js} +69 -12
- package/dist/qr-http-server-CDO6o2nr.js.map +1 -0
- package/dist/{qr-http-server-0uN5jxLW.cjs → qr-http-server-D0v9ooAD.cjs} +69 -12
- package/dist/qr-http-server-D0v9ooAD.cjs.map +1 -0
- package/dist/{qr-http-server-TQG61eI4.js → qr-http-server-DznDIcJF.js} +69 -12
- package/dist/qr-http-server-DznDIcJF.js.map +1 -0
- package/dist/{qr-http-server-BTjpFS3p.cjs → qr-http-server-jMC1nVqY.cjs} +69 -12
- package/dist/qr-http-server-jMC1nVqY.cjs.map +1 -0
- package/dist/{tunnel-BXAWl2tI.cjs → tunnel-D7f-0enB.cjs} +3 -2
- package/dist/{tunnel-BXAWl2tI.cjs.map → tunnel-D7f-0enB.cjs.map} +1 -1
- package/dist/{tunnel-BxGnLAat.js → tunnel-km3KkZrF.js} +3 -2
- package/dist/{tunnel-BxGnLAat.js.map → tunnel-km3KkZrF.js.map} +1 -1
- package/dist/unplugin/index.cjs +1 -1
- package/dist/unplugin/index.js +1 -1
- package/dist/unplugin/tunnel.cjs +2 -1
- package/dist/unplugin/tunnel.cjs.map +1 -1
- package/dist/unplugin/tunnel.d.cts.map +1 -1
- package/dist/unplugin/tunnel.d.ts.map +1 -1
- package/dist/unplugin/tunnel.js +2 -1
- package/dist/unplugin/tunnel.js.map +1 -1
- package/package.json +1 -1
- package/dist/qr-http-server-0uN5jxLW.cjs.map +0 -1
- package/dist/qr-http-server-BTjpFS3p.cjs.map +0 -1
- package/dist/qr-http-server-Ditd2ndz.js.map +0 -1
- package/dist/qr-http-server-TQG61eI4.js.map +0 -1
package/dist/mcp/cli.js
CHANGED
|
@@ -1169,9 +1169,8 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1169
1169
|
* Chii serves its own DevTools frontend at
|
|
1170
1170
|
* `<relayHttpBaseUrl>/front_end/chii_app.html`. The `ws=` (plain HTTP relay)
|
|
1171
1171
|
* or `wss=` (HTTPS relay) query parameter is a URL-encoded string of the form
|
|
1172
|
-
* `<relay-host>/client/<uuid>?target=<id
|
|
1173
|
-
*
|
|
1174
|
-
* `chii/public/index.js`).
|
|
1172
|
+
* `<relay-host>/client/<uuid>?target=<id>&at=<totp>` — the same format used
|
|
1173
|
+
* by Chii's own target list page (derived from `chii/public/index.js`).
|
|
1175
1174
|
*
|
|
1176
1175
|
* The `at=` TOTP code is minted at call time via `mintTotp()`. It is valid
|
|
1177
1176
|
* for ~3 minutes (relay gate accepts ±RELAY_VERIFY_SKEW_STEPS=6 steps =
|
|
@@ -1179,6 +1178,15 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1179
1178
|
* If the window expires before the browser connects, the relay will reject the
|
|
1180
1179
|
* WebSocket upgrade with close code 4401.
|
|
1181
1180
|
*
|
|
1181
|
+
* FAIL-CLOSED (issue #509): `mintTotp` is REQUIRED. When omitted (i.e.
|
|
1182
|
+
* `undefined`), this function returns `null` — the caller must treat `null` as
|
|
1183
|
+
* "inspector not yet available" and show a waiting hint instead of a broken
|
|
1184
|
+
* link. Relay sessions gate every WS upgrade with TOTP (#452), so a URL built
|
|
1185
|
+
* without `at=` would be rejected with WS 4401 immediately — there is no
|
|
1186
|
+
* non-TOTP relay path in production. Returning `null` surfaces this cleanly as
|
|
1187
|
+
* a "TOTP not yet configured" state rather than silently producing a URL that
|
|
1188
|
+
* will always fail at the WS handshake.
|
|
1189
|
+
*
|
|
1182
1190
|
* SECRET-HANDLING: `mintTotp` returns a code, not a secret. The code is
|
|
1183
1191
|
* embedded in the `wss=` parameter (inside the `at=` param) of the returned
|
|
1184
1192
|
* URL. Callers MUST NOT log the returned URL to stdout (stderr is OK — it is
|
|
@@ -1187,11 +1195,14 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1187
1195
|
* @param relayHttpBaseUrl - Local HTTP base URL of the Chii relay, e.g.
|
|
1188
1196
|
* `http://127.0.0.1:9100`. No trailing slash.
|
|
1189
1197
|
* @param targetId - Chii target id (from `GET <relay>/targets`).
|
|
1190
|
-
* @param mintTotp -
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1198
|
+
* @param mintTotp - Function that returns a fresh 6-digit TOTP code string.
|
|
1199
|
+
* Called at most once. **Required** — when `undefined`, the function returns
|
|
1200
|
+
* `null` (fail-closed: no `at=` param means the relay WS gate rejects the
|
|
1201
|
+
* handshake, so a null result is safer than a URL that always 404s).
|
|
1193
1202
|
* @param panel - Initial panel. Defaults to `"console"`.
|
|
1194
1203
|
*
|
|
1204
|
+
* @returns The inspector URL string, or `null` when `mintTotp` is absent.
|
|
1205
|
+
*
|
|
1195
1206
|
* @example
|
|
1196
1207
|
* buildChiiInspectorUrl(
|
|
1197
1208
|
* 'http://127.0.0.1:9100',
|
|
@@ -1201,6 +1212,7 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1201
1212
|
* // → 'http://127.0.0.1:9100/front_end/chii_app.html?ws=127.0.0.1%3A9100%2Fclient%2F<uuid>%3Ftarget%3Dabc123%26at%3D<code>'
|
|
1202
1213
|
*/
|
|
1203
1214
|
function buildChiiInspectorUrl(relayHttpBaseUrl, targetId, mintTotp, panel = "console") {
|
|
1215
|
+
if (!mintTotp) return null;
|
|
1204
1216
|
let relayHost;
|
|
1205
1217
|
let wsParamName;
|
|
1206
1218
|
try {
|
|
@@ -1212,11 +1224,8 @@ function buildChiiInspectorUrl(relayHttpBaseUrl, targetId, mintTotp, panel = "co
|
|
|
1212
1224
|
wsParamName = /^https:/i.test(relayHttpBaseUrl) ? "wss" : "ws";
|
|
1213
1225
|
}
|
|
1214
1226
|
const clientId = `devtools-opener-${Date.now().toString(36)}`;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
const code = mintTotp();
|
|
1218
|
-
wsPath += `&at=${encodeURIComponent(code)}`;
|
|
1219
|
-
}
|
|
1227
|
+
const code = mintTotp();
|
|
1228
|
+
const wsPath = `${relayHost}/client/${clientId}?target=${encodeURIComponent(targetId)}&at=${encodeURIComponent(code)}`;
|
|
1220
1229
|
const params = new URLSearchParams({
|
|
1221
1230
|
[wsParamName]: wsPath,
|
|
1222
1231
|
panel
|
|
@@ -1325,6 +1334,10 @@ var AutoDevtoolsOpener = class {
|
|
|
1325
1334
|
if (!options.targetId) return;
|
|
1326
1335
|
this._opened = true;
|
|
1327
1336
|
const inspectorUrl = buildChiiInspectorUrl(options.relayHttpBaseUrl, options.targetId, options.mintTotp);
|
|
1337
|
+
if (inspectorUrl === null) {
|
|
1338
|
+
process.stderr.write("[ait-debug] 기기가 연결됐습니다 — TOTP secret 미설정으로 인스펙터 URL을 생성할 수 없습니다.\n[ait-debug] relay 세션은 AIT_DEBUG_TOTP_SECRET 설정이 필요합니다.\n");
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1328
1341
|
process.stderr.write(`[ait-debug] 기기가 연결됐습니다 — Chii DevTools를 자동으로 엽니다.
|
|
1329
1342
|
[ait-debug] DevTools URL: ${inspectorUrl}\n[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)
|
|
1330
1343
|
[ait-debug] 주의: URL의 at= 코드는 ~3분 안에서만 유효합니다.
|
|
@@ -2096,6 +2109,9 @@ const en = {
|
|
|
2096
2109
|
"dashboard.pages.empty": "No attached pages",
|
|
2097
2110
|
"dashboard.url.copy": "Copy",
|
|
2098
2111
|
"dashboard.url.copied": "Copied",
|
|
2112
|
+
"dashboard.inspector.section": "Inspector",
|
|
2113
|
+
"dashboard.inspector.open": "Open inspector",
|
|
2114
|
+
"dashboard.inspector.waiting": "Inspector URL pending — appears after a page attaches",
|
|
2099
2115
|
"attach.title": "AIT Debug Session — QR Scan",
|
|
2100
2116
|
"attach.deployment": "deployment: {label}",
|
|
2101
2117
|
"attach.steps.section": "How to scan",
|
|
@@ -2139,6 +2155,7 @@ const en = {
|
|
|
2139
2155
|
"launcher.diagNo": "no",
|
|
2140
2156
|
"launcher.letterboxDetected": "Display area is {pt}pt short — likely an iOS standalone letterbox. Removing and re-adding the launcher to the home screen may fix it.",
|
|
2141
2157
|
"launcher.navbar.defaultTitle": "Mini App",
|
|
2158
|
+
"launcher.navbar.back": "Back",
|
|
2142
2159
|
"launcher.navbar.menu": "Menu",
|
|
2143
2160
|
"launcher.navbar.close": "Close",
|
|
2144
2161
|
"launcher.navbar.menuRescan": "Rescan",
|
|
@@ -2344,6 +2361,9 @@ const tables = {
|
|
|
2344
2361
|
"dashboard.pages.empty": "attach된 페이지 없음",
|
|
2345
2362
|
"dashboard.url.copy": "복사",
|
|
2346
2363
|
"dashboard.url.copied": "복사됨",
|
|
2364
|
+
"dashboard.inspector.section": "인스펙터",
|
|
2365
|
+
"dashboard.inspector.open": "인스펙터 열기",
|
|
2366
|
+
"dashboard.inspector.waiting": "인스펙터 URL 대기 중 (페이지 attach 후 표시됩니다)",
|
|
2347
2367
|
"attach.title": "AIT 디버그 세션 — QR 스캔",
|
|
2348
2368
|
"attach.deployment": "deployment: {label}",
|
|
2349
2369
|
"attach.steps.section": "스캔 절차",
|
|
@@ -2387,6 +2407,7 @@ const tables = {
|
|
|
2387
2407
|
"launcher.diagNo": "아니요",
|
|
2388
2408
|
"launcher.letterboxDetected": "표시 영역이 {pt}pt 부족합니다 — iOS standalone letterbox로 보입니다. 런처를 홈 화면에서 제거 후 다시 설치하면 해소될 수 있어요.",
|
|
2389
2409
|
"launcher.navbar.defaultTitle": "미니앱",
|
|
2410
|
+
"launcher.navbar.back": "뒤로가기",
|
|
2390
2411
|
"launcher.navbar.menu": "메뉴",
|
|
2391
2412
|
"launcher.navbar.close": "닫기",
|
|
2392
2413
|
"launcher.navbar.menuRescan": "다시 스캔",
|
|
@@ -2479,6 +2500,14 @@ img.qr {
|
|
|
2479
2500
|
}
|
|
2480
2501
|
.copy-btn:hover { background: #30363d; }
|
|
2481
2502
|
.hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
|
|
2503
|
+
.inspector-link {
|
|
2504
|
+
display: inline-block; margin-top: 0.5rem;
|
|
2505
|
+
padding: 0.45rem 1rem; border-radius: 6px;
|
|
2506
|
+
background: #1f6feb; color: #fff; font-size: 0.85rem; font-weight: 600;
|
|
2507
|
+
text-decoration: none; text-align: center;
|
|
2508
|
+
}
|
|
2509
|
+
.inspector-link:hover { background: #388bfd; }
|
|
2510
|
+
.inspector-hint { display: inline-block; margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.45; }
|
|
2482
2511
|
ul { margin: 0; padding-left: 1.25rem; }
|
|
2483
2512
|
li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
|
|
2484
2513
|
li.empty { opacity: 0.4; list-style: none; padding-left: 0; }
|
|
@@ -2488,7 +2517,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0; }
|
|
|
2488
2517
|
.lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
|
|
2489
2518
|
.lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
|
|
2490
2519
|
.lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
|
|
2491
|
-
</style></head><body><h1>AIT 디버그 Dashboard</h1>__LANG_SWITCHER__<p class="updated" id="updated">마지막 갱신: __NOW__</p><section><h2>터널 상태</h2><span class="status __TUNNEL_CLASS__" id="tunnel-status">__TUNNEL_STATUS__</span></section><hr/><section><h2>Attach QR</h2><div id="attach-section">__ATTACH_SECTION__</div></section>__PAGES_SECTION__</body></html>`;
|
|
2520
|
+
</style></head><body><h1>AIT 디버그 Dashboard</h1>__LANG_SWITCHER__<p class="updated" id="updated">마지막 갱신: __NOW__</p><section><h2>터널 상태</h2><span class="status __TUNNEL_CLASS__" id="tunnel-status">__TUNNEL_STATUS__</span></section><hr/><section><h2>Attach QR</h2><div id="attach-section">__ATTACH_SECTION__</div></section><hr/><section id="inspector-section"><h2>인스펙터</h2>__INSPECTOR_SECTION__</section>__PAGES_SECTION__</body></html>`;
|
|
2492
2521
|
const attachChromeHtmlKoSandbox = `<!DOCTYPE html>
|
|
2493
2522
|
<html lang="ko"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="__QR_DATA_URL__"/><title>AIT 디버그 세션 — QR 스캔</title><style>
|
|
2494
2523
|
*, *::before, *::after { box-sizing: border-box; }
|
|
@@ -2631,6 +2660,14 @@ img.qr {
|
|
|
2631
2660
|
}
|
|
2632
2661
|
.copy-btn:hover { background: #30363d; }
|
|
2633
2662
|
.hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
|
|
2663
|
+
.inspector-link {
|
|
2664
|
+
display: inline-block; margin-top: 0.5rem;
|
|
2665
|
+
padding: 0.45rem 1rem; border-radius: 6px;
|
|
2666
|
+
background: #1f6feb; color: #fff; font-size: 0.85rem; font-weight: 600;
|
|
2667
|
+
text-decoration: none; text-align: center;
|
|
2668
|
+
}
|
|
2669
|
+
.inspector-link:hover { background: #388bfd; }
|
|
2670
|
+
.inspector-hint { display: inline-block; margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.45; }
|
|
2634
2671
|
ul { margin: 0; padding-left: 1.25rem; }
|
|
2635
2672
|
li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
|
|
2636
2673
|
li.empty { opacity: 0.4; list-style: none; padding-left: 0; }
|
|
@@ -2640,7 +2677,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0; }
|
|
|
2640
2677
|
.lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
|
|
2641
2678
|
.lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
|
|
2642
2679
|
.lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
|
|
2643
|
-
</style></head><body><h1>AIT Debug Dashboard</h1>__LANG_SWITCHER__<p class="updated" id="updated">Last updated: __NOW__</p><section><h2>Tunnel status</h2><span class="status __TUNNEL_CLASS__" id="tunnel-status">__TUNNEL_STATUS__</span></section><hr/><section><h2>Attach QR</h2><div id="attach-section">__ATTACH_SECTION__</div></section>__PAGES_SECTION__</body></html>`;
|
|
2680
|
+
</style></head><body><h1>AIT Debug Dashboard</h1>__LANG_SWITCHER__<p class="updated" id="updated">Last updated: __NOW__</p><section><h2>Tunnel status</h2><span class="status __TUNNEL_CLASS__" id="tunnel-status">__TUNNEL_STATUS__</span></section><hr/><section><h2>Attach QR</h2><div id="attach-section">__ATTACH_SECTION__</div></section><hr/><section id="inspector-section"><h2>Inspector</h2>__INSPECTOR_SECTION__</section>__PAGES_SECTION__</body></html>`;
|
|
2644
2681
|
const attachChromeHtmlEnSandbox = `<!DOCTYPE html>
|
|
2645
2682
|
<html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="__QR_DATA_URL__"/><title>AIT Debug Session — QR Scan</title><style>
|
|
2646
2683
|
*, *::before, *::after { box-sizing: border-box; }
|
|
@@ -2817,13 +2854,15 @@ function buildLangSwitcher(path, existingParams, locale, s) {
|
|
|
2817
2854
|
*
|
|
2818
2855
|
* 동적 파트 분류:
|
|
2819
2856
|
* - "token-fill": 단일 값 교체 (__NOW__, __TUNNEL_CLASS__, __TUNNEL_STATUS__,
|
|
2820
|
-
* __ATTACH_SECTION__)
|
|
2857
|
+
* __ATTACH_SECTION__, __INSPECTOR_SECTION__)
|
|
2821
2858
|
* - "runtime builder": 가변 길이 구조 (__PAGES_SECTION__ — 조건부 렌더 + 가변 rows)
|
|
2822
2859
|
* - "suffix": inline SSE <script> (빌드 파이프라인 없는 클라이언트 스크립트, locale
|
|
2823
2860
|
* aware 문자열 포함)
|
|
2824
2861
|
*
|
|
2825
2862
|
* SECRET-HANDLING:
|
|
2826
2863
|
* - attachUrl은 url-box 안에서만 노출 (TOTP at= 코드 캡슐 그대로).
|
|
2864
|
+
* - inspectorUrl은 anchor href 안에서만 노출 (TOTP at= 코드 캡슐 그대로).
|
|
2865
|
+
* relay host + TOTP 코드가 담길 수 있으나 대시보드 HTML은 의도된 transport.
|
|
2827
2866
|
* - tunnel wssUrl은 "터널 연결됨" 상태 표시에서 UP/DOWN만 노출.
|
|
2828
2867
|
* wssUrl 값 자체는 dashboard HTML에 넣지 않는다.
|
|
2829
2868
|
*/
|
|
@@ -2838,6 +2877,9 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
|
|
|
2838
2877
|
const copyLabel = escapeHtml(s("dashboard.url.copy"));
|
|
2839
2878
|
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>`;
|
|
2840
2879
|
} else attachSection = `<p class="hint">${escapeHtml(s("dashboard.attach.hint"))}</p>`;
|
|
2880
|
+
let inspectorSection;
|
|
2881
|
+
if (state.inspectorUrl) inspectorSection = `<a class="inspector-link" id="inspector-link" href="${escapeHtml(state.inspectorUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtml(s("dashboard.inspector.open"))}</a>`;
|
|
2882
|
+
else inspectorSection = `<span class="inspector-hint" id="inspector-link">${escapeHtml(s("dashboard.inspector.waiting"))}</span>`;
|
|
2841
2883
|
const pagesSection = state.pages === null ? "" : `<hr /><section id="pages-section"><h2>${escapeHtml(s("dashboard.pages.section"))}</h2><ul id="pages-list">${state.pages.length > 0 ? state.pages.map((p) => {
|
|
2842
2884
|
return `<li><span class="page-id">${escapeHtml(p.id)}</span> <span class="page-url">${escapeHtml(p.url.slice(0, 120))}</span></li>`;
|
|
2843
2885
|
}).join("\n") : `<li class="empty">${escapeHtml(s("dashboard.pages.empty"))}</li>`}</ul></section>`;
|
|
@@ -2848,10 +2890,12 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
|
|
|
2848
2890
|
attachHint: JSON.stringify(s("dashboard.attach.hint")),
|
|
2849
2891
|
copyLabel: JSON.stringify(s("dashboard.url.copy")),
|
|
2850
2892
|
copiedLabel: JSON.stringify(s("dashboard.url.copied")),
|
|
2893
|
+
inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
|
|
2894
|
+
inspectorWaitingLabel: JSON.stringify(s("dashboard.inspector.waiting")),
|
|
2851
2895
|
dashboardSurface: true
|
|
2852
2896
|
};
|
|
2853
2897
|
const langSwitcher = buildLangSwitcher(path, params, locale, s);
|
|
2854
|
-
const filled = dashboardChromeByLocale[locale].replaceAll("__LANG_SWITCHER__", langSwitcher).replaceAll("__NOW__", escapeHtml(now)).replaceAll("__TUNNEL_CLASS__", tunnelClass).replaceAll("__TUNNEL_STATUS__", escapeHtml(tunnelStatus)).replaceAll("__ATTACH_SECTION__", attachSection).replaceAll("__PAGES_SECTION__", pagesSection);
|
|
2898
|
+
const filled = dashboardChromeByLocale[locale].replaceAll("__LANG_SWITCHER__", langSwitcher).replaceAll("__NOW__", escapeHtml(now)).replaceAll("__TUNNEL_CLASS__", tunnelClass).replaceAll("__TUNNEL_STATUS__", escapeHtml(tunnelStatus)).replaceAll("__ATTACH_SECTION__", attachSection).replaceAll("__INSPECTOR_SECTION__", inspectorSection).replaceAll("__PAGES_SECTION__", pagesSection);
|
|
2855
2899
|
const sseScript = buildSseScript(sseStrings);
|
|
2856
2900
|
return filled.replace("</body>", `${sseScript}\n</body>`);
|
|
2857
2901
|
}
|
|
@@ -2889,6 +2933,8 @@ function buildSseScript(strings) {
|
|
|
2889
2933
|
var ATTACH_HINT = ${strings.attachHint};
|
|
2890
2934
|
var COPY_LABEL = ${strings.copyLabel};
|
|
2891
2935
|
var COPIED_LABEL = ${strings.copiedLabel};
|
|
2936
|
+
var INSPECTOR_OPEN_LABEL = ${strings.inspectorOpenLabel};
|
|
2937
|
+
var INSPECTOR_WAITING_LABEL = ${strings.inspectorWaitingLabel};
|
|
2892
2938
|
|
|
2893
2939
|
// ── 클립보드 복사 헬퍼 ────────────────────────────────────────────────
|
|
2894
2940
|
function copyText(text) {
|
|
@@ -3002,6 +3048,17 @@ function buildSseScript(strings) {
|
|
|
3002
3048
|
sec.innerHTML = '<p class=\\"hint\\">' + ATTACH_HINT + '</p>';`}
|
|
3003
3049
|
}
|
|
3004
3050
|
}
|
|
3051
|
+
// 인스펙터 링크 갱신 — #inspector-link (#503).
|
|
3052
|
+
// SECRET-HANDLING: inspectorUrl을 console.log 등으로 출력하지 않는다.
|
|
3053
|
+
var insp = document.getElementById('inspector-link');
|
|
3054
|
+
if (insp) {
|
|
3055
|
+
if (s.inspectorUrl) {
|
|
3056
|
+
var safeInspUrl = String(s.inspectorUrl).slice(0, 2000).replace(/[<>&"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; });
|
|
3057
|
+
insp.outerHTML = '<a class=\\"inspector-link\\" id=\\"inspector-link\\" href=\\"' + safeInspUrl + '\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">' + INSPECTOR_OPEN_LABEL + '</a>';
|
|
3058
|
+
} else {
|
|
3059
|
+
insp.outerHTML = '<span class=\\"inspector-hint\\" id=\\"inspector-link\\">' + INSPECTOR_WAITING_LABEL + '</span>';
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3005
3062
|
// 갱신 시각 (dashboard만 #updated 요소 있음)
|
|
3006
3063
|
var upd = document.getElementById('updated');
|
|
3007
3064
|
if (upd) upd.textContent = upd.textContent.replace(/[^ ]+$/, new Date().toISOString());
|
|
@@ -3046,6 +3103,8 @@ function buildAttachHtml(qrDataUrl, safeLabel, safeAttachUrl, locale, path = "/a
|
|
|
3046
3103
|
attachHint: JSON.stringify(s("dashboard.attach.hint")),
|
|
3047
3104
|
copyLabel: JSON.stringify(s("dashboard.url.copy")),
|
|
3048
3105
|
copiedLabel: JSON.stringify(s("dashboard.url.copied")),
|
|
3106
|
+
inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
|
|
3107
|
+
inspectorWaitingLabel: JSON.stringify(s("dashboard.inspector.waiting")),
|
|
3049
3108
|
dashboardSurface: false
|
|
3050
3109
|
});
|
|
3051
3110
|
return filled.replace("</body>", `${sseScript}\n</body>`);
|
|
@@ -3056,8 +3115,9 @@ function buildAttachHtml(qrDataUrl, safeLabel, safeAttachUrl, locale, path = "/a
|
|
|
3056
3115
|
*
|
|
3057
3116
|
* @param getDashboardState - dashboard 상태를 반환하는 클로저. 주입 시 `GET /` dashboard와
|
|
3058
3117
|
* `GET /events` SSE 스트림이 활성화된다. 미주입 시 두 라우트는 204/서비스 없음으로 응답.
|
|
3118
|
+
* @param options - 서버 옵션. `sseRefreshIntervalMs`로 idle 탭 TOTP 만료 방지 주기를 조정.
|
|
3059
3119
|
*/
|
|
3060
|
-
async function startQrHttpServer(getDashboardState) {
|
|
3120
|
+
async function startQrHttpServer(getDashboardState, options) {
|
|
3061
3121
|
const { default: QRCode } = await import("qrcode");
|
|
3062
3122
|
/** SSE 활성 연결 목록 — `notifyStateChange()` 시 전체 push. */
|
|
3063
3123
|
const sseClients = [];
|
|
@@ -3069,7 +3129,8 @@ async function startQrHttpServer(getDashboardState) {
|
|
|
3069
3129
|
wssUrl: state.tunnel.wssUrl
|
|
3070
3130
|
},
|
|
3071
3131
|
pages: state.pages,
|
|
3072
|
-
attachUrl: state.attachUrl
|
|
3132
|
+
attachUrl: state.attachUrl,
|
|
3133
|
+
inspectorUrl: state.inspectorUrl ?? null
|
|
3073
3134
|
});
|
|
3074
3135
|
res.write(`data: ${payload}\n\n`);
|
|
3075
3136
|
}
|
|
@@ -3189,19 +3250,28 @@ async function startQrHttpServer(getDashboardState) {
|
|
|
3189
3250
|
const address = server.address();
|
|
3190
3251
|
if (!address || typeof address === "string") throw new Error("qr-http-server: server.address()가 예상하지 못한 형태입니다.");
|
|
3191
3252
|
const port = address.port;
|
|
3253
|
+
/** idle 탭 TOTP 만료 방지용 주기 SSE 갱신 interval. */
|
|
3254
|
+
function notifyStateChangeInternal() {
|
|
3255
|
+
if (!getDashboardState) return;
|
|
3256
|
+
const state = getDashboardState();
|
|
3257
|
+
for (const client of sseClients) try {
|
|
3258
|
+
pushStateToClient(client, state);
|
|
3259
|
+
} catch {}
|
|
3260
|
+
}
|
|
3261
|
+
const refreshIntervalMs = options?.sseRefreshIntervalMs ?? 9e4;
|
|
3262
|
+
const refreshHandle = setInterval(() => {
|
|
3263
|
+
if (sseClients.length > 0 && getDashboardState) notifyStateChangeInternal();
|
|
3264
|
+
}, refreshIntervalMs).unref();
|
|
3192
3265
|
return {
|
|
3193
3266
|
port,
|
|
3194
3267
|
buildAttachPageUrl(attachUrl) {
|
|
3195
3268
|
return `http://127.0.0.1:${port}/attach?u=${encodeURIComponent(attachUrl)}`;
|
|
3196
3269
|
},
|
|
3197
3270
|
notifyStateChange() {
|
|
3198
|
-
|
|
3199
|
-
const state = getDashboardState();
|
|
3200
|
-
for (const client of sseClients) try {
|
|
3201
|
-
pushStateToClient(client, state);
|
|
3202
|
-
} catch {}
|
|
3271
|
+
notifyStateChangeInternal();
|
|
3203
3272
|
},
|
|
3204
3273
|
close() {
|
|
3274
|
+
clearInterval(refreshHandle);
|
|
3205
3275
|
return new Promise((resolve, reject) => {
|
|
3206
3276
|
server.close((err) => err ? reject(err) : resolve());
|
|
3207
3277
|
});
|
|
@@ -4590,7 +4660,7 @@ async function readMcpSdkVersion() {
|
|
|
4590
4660
|
* some test environments that skip the build step).
|
|
4591
4661
|
*/
|
|
4592
4662
|
function readDevtoolsVersion() {
|
|
4593
|
-
return "0.1.
|
|
4663
|
+
return "0.1.75";
|
|
4594
4664
|
}
|
|
4595
4665
|
/**
|
|
4596
4666
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -5094,7 +5164,7 @@ function createDebugServer(deps) {
|
|
|
5094
5164
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
5095
5165
|
const server = new Server({
|
|
5096
5166
|
name: "ait-debug",
|
|
5097
|
-
version: "0.1.
|
|
5167
|
+
version: "0.1.75"
|
|
5098
5168
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
5099
5169
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
5100
5170
|
const conn = router.active;
|
|
@@ -5693,37 +5763,52 @@ function errorResult(err, name, isLocal = false) {
|
|
|
5693
5763
|
return classifyToolError(err, name, isLocal);
|
|
5694
5764
|
}
|
|
5695
5765
|
/**
|
|
5696
|
-
* Starts a polling watcher that detects
|
|
5766
|
+
* Starts a polling watcher that detects target-set changes on
|
|
5697
5767
|
* `connection.listTargets()` and sends a `notifications/tools/list_changed`
|
|
5698
5768
|
* notification on the given server.
|
|
5699
5769
|
*
|
|
5700
5770
|
* The watcher polls every `intervalMs` (default 1 000 ms). It fires
|
|
5701
|
-
* `server.sendToolListChanged()`
|
|
5702
|
-
*
|
|
5771
|
+
* `server.sendToolListChanged()` + `onAttach()` whenever the sorted target-id
|
|
5772
|
+
* signature changes AND the new target set is non-empty. This covers:
|
|
5773
|
+
* - 0→N first attach
|
|
5774
|
+
* - 1→1 target replacement (same count, different id — e.g. rescan)
|
|
5775
|
+
* - N→M any change where the result is still non-empty
|
|
5776
|
+
*
|
|
5777
|
+
* Full detach (→ empty) updates the stored signature but does NOT fire the
|
|
5778
|
+
* callback — `onAttach` semantics are about a live target being present.
|
|
5779
|
+
*
|
|
5780
|
+
* The interval is **never cleared automatically** — it keeps running until
|
|
5781
|
+
* `stop()` is called during shutdown. This ensures that a target replacement
|
|
5782
|
+
* after the first attach is always detected.
|
|
5703
5783
|
*
|
|
5704
|
-
* `
|
|
5705
|
-
* already attached). Use this to trigger side-effects such as
|
|
5706
|
-
*
|
|
5707
|
-
* the previous behaviour exactly.
|
|
5784
|
+
* `onAttach` is called on every non-empty signature change (or immediately when
|
|
5785
|
+
* already attached). Use this to trigger side-effects such as pushing a fresh
|
|
5786
|
+
* SSE state to open dashboard tabs (issue #509). The callback is optional;
|
|
5787
|
+
* omitting it preserves the previous behaviour exactly.
|
|
5708
5788
|
*
|
|
5709
5789
|
* SECRET-HANDLING: target `id`/`title`/`url` are not written to any log here.
|
|
5710
5790
|
* Only an attach-detected stderr line is emitted (no target details).
|
|
5711
5791
|
*
|
|
5712
5792
|
* @returns `stop` — call this during shutdown to clear the interval.
|
|
5713
5793
|
*/
|
|
5714
|
-
function startAttachWatcher(connection, server, intervalMs = 1e3,
|
|
5715
|
-
|
|
5716
|
-
|
|
5794
|
+
function startAttachWatcher(connection, server, intervalMs = 1e3, onAttach) {
|
|
5795
|
+
/** Sorted, comma-joined target-id string — '' means no targets attached. */
|
|
5796
|
+
function signature() {
|
|
5797
|
+
return connection.listTargets().map((t) => t.id).sort().join(",");
|
|
5798
|
+
}
|
|
5799
|
+
let lastSignature = signature();
|
|
5800
|
+
if (lastSignature !== "") {
|
|
5717
5801
|
server.sendToolListChanged();
|
|
5718
|
-
|
|
5802
|
+
onAttach?.();
|
|
5719
5803
|
}
|
|
5720
5804
|
const handle = setInterval(() => {
|
|
5721
|
-
const
|
|
5722
|
-
if (
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5805
|
+
const current = signature();
|
|
5806
|
+
if (current !== lastSignature) {
|
|
5807
|
+
lastSignature = current;
|
|
5808
|
+
if (current !== "") {
|
|
5809
|
+
server.sendToolListChanged();
|
|
5810
|
+
onAttach?.();
|
|
5811
|
+
}
|
|
5727
5812
|
}
|
|
5728
5813
|
}, intervalMs);
|
|
5729
5814
|
return { stop() {
|
|
@@ -6020,6 +6105,15 @@ var DualConnectionRouter = class {
|
|
|
6020
6105
|
get activeRelayOrigin() {
|
|
6021
6106
|
return this.activeFamily?.relayOrigin;
|
|
6022
6107
|
}
|
|
6108
|
+
/**
|
|
6109
|
+
* Local HTTP base URL of the Chii relay for the currently-active family (#503).
|
|
6110
|
+
* Used by `getDashboardState` to build the inspector URL via `buildChiiInspectorUrl`.
|
|
6111
|
+
* Returns `undefined` when no relay family is active (local/mock mode).
|
|
6112
|
+
* SECRET-HANDLING: not logged — callers must not write this to stdout/logs.
|
|
6113
|
+
*/
|
|
6114
|
+
get activeRelayHttpUrl() {
|
|
6115
|
+
return this.activeFamily?.relayHttpUrl;
|
|
6116
|
+
}
|
|
6023
6117
|
/** Every booted family (for unified shutdown). All families are lazy (#396). */
|
|
6024
6118
|
bootedFamilies() {
|
|
6025
6119
|
return [...this.lazyFamilies.values()];
|
|
@@ -6147,18 +6241,25 @@ async function runDebugServer(options = {}) {
|
|
|
6147
6241
|
return router.active;
|
|
6148
6242
|
});
|
|
6149
6243
|
let lastAttachParts = null;
|
|
6150
|
-
const getDashboardState = () =>
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6244
|
+
const getDashboardState = () => {
|
|
6245
|
+
const targets = router.active.listTargets();
|
|
6246
|
+
const relayHttpUrl = router.activeRelayHttpUrl;
|
|
6247
|
+
const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
|
|
6248
|
+
const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
|
|
6249
|
+
return {
|
|
6250
|
+
tunnel: {
|
|
6251
|
+
up: router.relayTunnelStatus().up,
|
|
6252
|
+
wssUrl: router.relayTunnelStatus().wssUrl
|
|
6253
|
+
},
|
|
6254
|
+
pages: targets.map((t) => ({
|
|
6255
|
+
id: t.id,
|
|
6256
|
+
url: t.url
|
|
6257
|
+
})),
|
|
6258
|
+
attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
|
|
6259
|
+
inspectorUrl,
|
|
6260
|
+
mode: deriveEnvironment(router.active.kind, getLiveIntent(), router.activeRelayOrigin)
|
|
6261
|
+
};
|
|
6262
|
+
};
|
|
6162
6263
|
let qrServer;
|
|
6163
6264
|
try {
|
|
6164
6265
|
qrServer = await startQrHttpServer(getDashboardState);
|
|
@@ -6313,17 +6414,24 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6313
6414
|
return router.active;
|
|
6314
6415
|
});
|
|
6315
6416
|
let lastAttachParts = null;
|
|
6316
|
-
const getDashboardState = () =>
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6417
|
+
const getDashboardState = () => {
|
|
6418
|
+
const targets = router.active.listTargets();
|
|
6419
|
+
const relayHttpUrl = router.activeRelayHttpUrl;
|
|
6420
|
+
const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
|
|
6421
|
+
const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
|
|
6422
|
+
return {
|
|
6423
|
+
tunnel: {
|
|
6424
|
+
up: router.relayTunnelStatus().up,
|
|
6425
|
+
wssUrl: router.relayTunnelStatus().wssUrl
|
|
6426
|
+
},
|
|
6427
|
+
pages: targets.map((t) => ({
|
|
6428
|
+
id: t.id,
|
|
6429
|
+
url: t.url
|
|
6430
|
+
})),
|
|
6431
|
+
attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
|
|
6432
|
+
inspectorUrl
|
|
6433
|
+
};
|
|
6434
|
+
};
|
|
6327
6435
|
let qrServer;
|
|
6328
6436
|
try {
|
|
6329
6437
|
qrServer = await startQrHttpServer(getDashboardState);
|
|
@@ -6462,17 +6570,24 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6462
6570
|
return router.active;
|
|
6463
6571
|
});
|
|
6464
6572
|
let lastAttachParts = null;
|
|
6465
|
-
const getDashboardState = () =>
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6573
|
+
const getDashboardState = () => {
|
|
6574
|
+
const targets = router.active.listTargets();
|
|
6575
|
+
const relayHttpUrl = router.activeRelayHttpUrl;
|
|
6576
|
+
const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
|
|
6577
|
+
const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
|
|
6578
|
+
return {
|
|
6579
|
+
tunnel: {
|
|
6580
|
+
up: router.relayTunnelStatus().up,
|
|
6581
|
+
wssUrl: router.relayTunnelStatus().wssUrl
|
|
6582
|
+
},
|
|
6583
|
+
pages: targets.map((t) => ({
|
|
6584
|
+
id: t.id,
|
|
6585
|
+
url: t.url
|
|
6586
|
+
})),
|
|
6587
|
+
attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
|
|
6588
|
+
inspectorUrl
|
|
6589
|
+
};
|
|
6590
|
+
};
|
|
6476
6591
|
let qrServer;
|
|
6477
6592
|
try {
|
|
6478
6593
|
qrServer = await startQrHttpServer(getDashboardState);
|
|
@@ -6990,7 +7105,7 @@ function createDevServer(deps = {}) {
|
|
|
6990
7105
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
6991
7106
|
const server = new Server({
|
|
6992
7107
|
name: "ait-devtools",
|
|
6993
|
-
version: "0.1.
|
|
7108
|
+
version: "0.1.75"
|
|
6994
7109
|
}, { capabilities: { tools: {} } });
|
|
6995
7110
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
6996
7111
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|