@ait-co/devtools 0.1.66 → 0.1.67
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/mcp/cli.js +168 -20
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/panel/index.js +6 -2
- package/dist/panel/index.js.map +1 -1
- package/dist/{qr-http-server-CMJmKkb8.cjs → qr-http-server-B5YndXcS.cjs} +166 -18
- package/dist/qr-http-server-B5YndXcS.cjs.map +1 -0
- package/dist/{qr-http-server-BuyQnaS6.js → qr-http-server-BUfbLGm1.js} +166 -18
- package/dist/qr-http-server-BUfbLGm1.js.map +1 -0
- package/dist/{qr-http-server-CLtsKfPF.js → qr-http-server-ChC7P6-H.js} +166 -18
- package/dist/qr-http-server-ChC7P6-H.js.map +1 -0
- package/dist/{qr-http-server-CQwQumPJ.cjs → qr-http-server-DRlwR54D.cjs} +166 -18
- package/dist/qr-http-server-DRlwR54D.cjs.map +1 -0
- package/dist/{tunnel-fm4hDfV-.js → tunnel-BNzbSCfB.js} +2 -2
- package/dist/{tunnel-fm4hDfV-.js.map → tunnel-BNzbSCfB.js.map} +1 -1
- package/dist/{tunnel-BpllDsRw.cjs → tunnel-CrlCX5sZ.cjs} +2 -2
- package/dist/{tunnel-BpllDsRw.cjs.map → tunnel-CrlCX5sZ.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-BuyQnaS6.js.map +0 -1
- package/dist/qr-http-server-CLtsKfPF.js.map +0 -1
- package/dist/qr-http-server-CMJmKkb8.cjs.map +0 -1
- package/dist/qr-http-server-CQwQumPJ.cjs.map +0 -1
package/dist/mcp/cli.js
CHANGED
|
@@ -1832,6 +1832,8 @@ const en = {
|
|
|
1832
1832
|
"dashboard.attach.hint": "Call the build_attach_url MCP tool to show the QR here.",
|
|
1833
1833
|
"dashboard.pages.section": "Connected Pages",
|
|
1834
1834
|
"dashboard.pages.empty": "No attached pages",
|
|
1835
|
+
"dashboard.url.copy": "Copy",
|
|
1836
|
+
"dashboard.url.copied": "Copied",
|
|
1835
1837
|
"attach.title": "AIT Debug Session — QR Scan",
|
|
1836
1838
|
"attach.deployment": "deployment: {label}",
|
|
1837
1839
|
"attach.steps.section": "How to scan",
|
|
@@ -2057,6 +2059,8 @@ const tables = {
|
|
|
2057
2059
|
"dashboard.attach.hint": "build_attach_url MCP tool을 호출하면 QR이 여기에 표시됩니다.",
|
|
2058
2060
|
"dashboard.pages.section": "연결된 Pages",
|
|
2059
2061
|
"dashboard.pages.empty": "attach된 페이지 없음",
|
|
2062
|
+
"dashboard.url.copy": "복사",
|
|
2063
|
+
"dashboard.url.copied": "복사됨",
|
|
2060
2064
|
"attach.title": "AIT 디버그 세션 — QR 스캔",
|
|
2061
2065
|
"attach.deployment": "deployment: {label}",
|
|
2062
2066
|
"attach.steps.section": "스캔 절차",
|
|
@@ -2152,12 +2156,24 @@ img.qr {
|
|
|
2152
2156
|
background: #fff; padding: 0.75rem; border-radius: 10px;
|
|
2153
2157
|
display: block; margin: 0.5rem auto;
|
|
2154
2158
|
}
|
|
2159
|
+
.url-row {
|
|
2160
|
+
display: flex; align-items: stretch; gap: 0; margin: 0.5rem 0 0;
|
|
2161
|
+
border-radius: 6px; border: 1px solid #30363d; overflow: hidden;
|
|
2162
|
+
}
|
|
2155
2163
|
.url-box {
|
|
2156
2164
|
font-family: monospace; font-size: 0.7rem;
|
|
2157
2165
|
word-break: break-all; opacity: 0.45;
|
|
2158
2166
|
background: #161b22; padding: 0.6rem 0.85rem;
|
|
2159
|
-
|
|
2167
|
+
flex: 1; cursor: pointer; border: none; border-radius: 0;
|
|
2168
|
+
}
|
|
2169
|
+
.url-box:hover { opacity: 0.65; }
|
|
2170
|
+
.copy-btn {
|
|
2171
|
+
flex-shrink: 0; padding: 0.4rem 0.7rem;
|
|
2172
|
+
background: #21262d; border: none; border-left: 1px solid #30363d;
|
|
2173
|
+
color: #58a6ff; font-size: 0.7rem; cursor: pointer; white-space: nowrap;
|
|
2174
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2160
2175
|
}
|
|
2176
|
+
.copy-btn:hover { background: #30363d; }
|
|
2161
2177
|
.hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
|
|
2162
2178
|
ul { margin: 0; padding-left: 1.25rem; }
|
|
2163
2179
|
li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
|
|
@@ -2191,17 +2207,29 @@ section { width: 100%; max-width: 480px; }
|
|
|
2191
2207
|
h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
|
|
2192
2208
|
ol, ul { margin: 0; padding-left: 1.25rem; }
|
|
2193
2209
|
li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
|
|
2210
|
+
.url-row {
|
|
2211
|
+
display: flex; align-items: stretch; gap: 0;
|
|
2212
|
+
border-radius: 6px; border: 1px solid #30363d; overflow: hidden;
|
|
2213
|
+
}
|
|
2194
2214
|
.url-box {
|
|
2195
2215
|
font-family: monospace; font-size: 0.72rem;
|
|
2196
2216
|
word-break: break-all; opacity: 0.4;
|
|
2197
2217
|
background: #161b22; padding: 0.75rem 1rem;
|
|
2198
|
-
|
|
2218
|
+
flex: 1; cursor: pointer; border: none; border-radius: 0;
|
|
2219
|
+
}
|
|
2220
|
+
.url-box:hover { opacity: 0.6; }
|
|
2221
|
+
.copy-btn {
|
|
2222
|
+
flex-shrink: 0; padding: 0.5rem 0.8rem;
|
|
2223
|
+
background: #21262d; border: none; border-left: 1px solid #30363d;
|
|
2224
|
+
color: #58a6ff; font-size: 0.75rem; cursor: pointer; white-space: nowrap;
|
|
2225
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2199
2226
|
}
|
|
2227
|
+
.copy-btn:hover { background: #30363d; }
|
|
2200
2228
|
hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0.5rem 0; }
|
|
2201
2229
|
.lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
|
|
2202
2230
|
.lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
|
|
2203
2231
|
.lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
|
|
2204
|
-
</style></head><body><h1>AIT 디버그 세션 — QR 스캔</h1>__LANG_SWITCHER__<p class="label">deployment: __SAFE_LABEL__</p><div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>스캔 절차</h2><ol><li>토스 앱을 실행하세요.</li><li>폰 카메라 앱으로 QR 코드를 스캔하세요.</li><li>팝업이 뜨면 <strong>"토스로 열기"</strong>를 탭하세요.</li><li>미니앱이 열리고 디버그 세션이 자동으로 attach됩니다.</li></ol></section><hr/><section><h2>진단 체크리스트</h2><ul><li><strong>토스 앱이 안 열리는 경우</strong> — 앱 버전 확인, 카메라 앱으로 스캔 (토스 앱 내 QR 리더 X)</li><li><strong>미니앱이 PREPARE 상태에서 멈추는 경우</strong> — deep-link에 <code>_deploymentId</code> 파라미터가 있는지 확인</li><li><strong>Chii 주입 실패 / 콘솔이 비어 있는 경우</strong> — 미니앱 번들에 <code>in-app</code> debug import가 있는지 확인</li><li><strong>TOTP gate Layer C가 비활성인 경우</strong> — relay 서버에 <code>AIT_DEBUG_TOTP_SECRET</code>이 설정돼 있는지 확인</li></ul></section><hr/><section><h2>URL (fallback)</h2><p class="url-box">__SAFE_ATTACH_URL__</p></section></body></html>`;
|
|
2232
|
+
</style></head><body><h1>AIT 디버그 세션 — QR 스캔</h1>__LANG_SWITCHER__<p class="label">deployment: __SAFE_LABEL__</p><div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>스캔 절차</h2><ol><li>토스 앱을 실행하세요.</li><li>폰 카메라 앱으로 QR 코드를 스캔하세요.</li><li>팝업이 뜨면 <strong>"토스로 열기"</strong>를 탭하세요.</li><li>미니앱이 열리고 디버그 세션이 자동으로 attach됩니다.</li></ol></section><hr/><section><h2>진단 체크리스트</h2><ul><li><strong>토스 앱이 안 열리는 경우</strong> — 앱 버전 확인, 카메라 앱으로 스캔 (토스 앱 내 QR 리더 X)</li><li><strong>미니앱이 PREPARE 상태에서 멈추는 경우</strong> — deep-link에 <code>_deploymentId</code> 파라미터가 있는지 확인</li><li><strong>Chii 주입 실패 / 콘솔이 비어 있는 경우</strong> — 미니앱 번들에 <code>in-app</code> debug import가 있는지 확인</li><li><strong>TOTP gate Layer C가 비활성인 경우</strong> — relay 서버에 <code>AIT_DEBUG_TOTP_SECRET</code>이 설정돼 있는지 확인</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="복사">복사</button></div></section></body></html>`;
|
|
2205
2233
|
const dashboardChromeHtmlEn = `<!DOCTYPE html>
|
|
2206
2234
|
<html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>AIT Debug Dashboard</title><style>
|
|
2207
2235
|
*, *::before, *::after { box-sizing: border-box; }
|
|
@@ -2225,12 +2253,24 @@ img.qr {
|
|
|
2225
2253
|
background: #fff; padding: 0.75rem; border-radius: 10px;
|
|
2226
2254
|
display: block; margin: 0.5rem auto;
|
|
2227
2255
|
}
|
|
2256
|
+
.url-row {
|
|
2257
|
+
display: flex; align-items: stretch; gap: 0; margin: 0.5rem 0 0;
|
|
2258
|
+
border-radius: 6px; border: 1px solid #30363d; overflow: hidden;
|
|
2259
|
+
}
|
|
2228
2260
|
.url-box {
|
|
2229
2261
|
font-family: monospace; font-size: 0.7rem;
|
|
2230
2262
|
word-break: break-all; opacity: 0.45;
|
|
2231
2263
|
background: #161b22; padding: 0.6rem 0.85rem;
|
|
2232
|
-
|
|
2264
|
+
flex: 1; cursor: pointer; border: none; border-radius: 0;
|
|
2265
|
+
}
|
|
2266
|
+
.url-box:hover { opacity: 0.65; }
|
|
2267
|
+
.copy-btn {
|
|
2268
|
+
flex-shrink: 0; padding: 0.4rem 0.7rem;
|
|
2269
|
+
background: #21262d; border: none; border-left: 1px solid #30363d;
|
|
2270
|
+
color: #58a6ff; font-size: 0.7rem; cursor: pointer; white-space: nowrap;
|
|
2271
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2233
2272
|
}
|
|
2273
|
+
.copy-btn:hover { background: #30363d; }
|
|
2234
2274
|
.hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
|
|
2235
2275
|
ul { margin: 0; padding-left: 1.25rem; }
|
|
2236
2276
|
li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
|
|
@@ -2264,17 +2304,29 @@ section { width: 100%; max-width: 480px; }
|
|
|
2264
2304
|
h2 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 0 0 0.5rem; }
|
|
2265
2305
|
ol, ul { margin: 0; padding-left: 1.25rem; }
|
|
2266
2306
|
li { margin-bottom: 0.4rem; font-size: 0.9rem; line-height: 1.5; }
|
|
2307
|
+
.url-row {
|
|
2308
|
+
display: flex; align-items: stretch; gap: 0;
|
|
2309
|
+
border-radius: 6px; border: 1px solid #30363d; overflow: hidden;
|
|
2310
|
+
}
|
|
2267
2311
|
.url-box {
|
|
2268
2312
|
font-family: monospace; font-size: 0.72rem;
|
|
2269
2313
|
word-break: break-all; opacity: 0.4;
|
|
2270
2314
|
background: #161b22; padding: 0.75rem 1rem;
|
|
2271
|
-
|
|
2315
|
+
flex: 1; cursor: pointer; border: none; border-radius: 0;
|
|
2316
|
+
}
|
|
2317
|
+
.url-box:hover { opacity: 0.6; }
|
|
2318
|
+
.copy-btn {
|
|
2319
|
+
flex-shrink: 0; padding: 0.5rem 0.8rem;
|
|
2320
|
+
background: #21262d; border: none; border-left: 1px solid #30363d;
|
|
2321
|
+
color: #58a6ff; font-size: 0.75rem; cursor: pointer; white-space: nowrap;
|
|
2322
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2272
2323
|
}
|
|
2324
|
+
.copy-btn:hover { background: #30363d; }
|
|
2273
2325
|
hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0.5rem 0; }
|
|
2274
2326
|
.lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
|
|
2275
2327
|
.lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
|
|
2276
2328
|
.lang-switcher a.active { font-weight: 700; text-decoration: underline; opacity: 1; }
|
|
2277
|
-
</style></head><body><h1>AIT Debug Session — QR Scan</h1>__LANG_SWITCHER__<p class="label">deployment: __SAFE_LABEL__</p><div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>How to scan</h2><ol><li>Open the Toss app.</li><li>Scan the QR code with your phone camera app.</li><li>Tap <strong>"Open in Toss"</strong> when the popup appears.</li><li>The mini-app opens and the debug session attaches automatically.</li></ol></section><hr/><section><h2>Troubleshooting checklist</h2><ul><li><strong>Toss app does not open</strong> — check app version; scan with the system camera app (not the Toss in-app QR reader)</li><li><strong>Mini-app stuck in PREPARE state</strong> — verify the deep-link has a <code>_deploymentId</code> parameter</li><li><strong>Chii injection failure / console is empty</strong> — verify the mini-app bundle has an <code>in-app</code> debug import</li><li><strong>TOTP gate Layer C is inactive</strong> — check that <code>AIT_DEBUG_TOTP_SECRET</code> is set on the relay server</li></ul></section><hr/><section><h2>URL (fallback)</h2><p class="url-box">__SAFE_ATTACH_URL__</p></section></body></html>`;
|
|
2329
|
+
</style></head><body><h1>AIT Debug Session — QR Scan</h1>__LANG_SWITCHER__<p class="label">deployment: __SAFE_LABEL__</p><div id="attach-section"><img class="qr" src="__QR_DATA_URL__" alt="attach QR"/></div><section><h2>How to scan</h2><ol><li>Open the Toss app.</li><li>Scan the QR code with your phone camera app.</li><li>Tap <strong>"Open in Toss"</strong> when the popup appears.</li><li>The mini-app opens and the debug session attaches automatically.</li></ol></section><hr/><section><h2>Troubleshooting checklist</h2><ul><li><strong>Toss app does not open</strong> — check app version; scan with the system camera app (not the Toss in-app QR reader)</li><li><strong>Mini-app stuck in PREPARE state</strong> — verify the deep-link has a <code>_deploymentId</code> parameter</li><li><strong>Chii injection failure / console is empty</strong> — verify the mini-app bundle has an <code>in-app</code> debug import</li><li><strong>TOTP gate Layer C is inactive</strong> — check that <code>AIT_DEBUG_TOTP_SECRET</code> is set on the relay server</li></ul></section><hr/><section id="url-section"><h2>URL (fallback)</h2><div class="url-row"><p class="url-box" id="url-box">__SAFE_ATTACH_URL__</p><button class="copy-btn" id="copy-btn" type="button" aria-label="Copy">Copy</button></div></section></body></html>`;
|
|
2278
2330
|
/** Map from Locale to the precompiled dashboard chrome string. */
|
|
2279
2331
|
const dashboardChromeByLocale = {
|
|
2280
2332
|
ko: dashboardChromeHtmlKo,
|
|
@@ -2335,8 +2387,11 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
|
|
|
2335
2387
|
const tunnelStatus = state.tunnel.up ? s("dashboard.tunnel.up") : s("dashboard.tunnel.down");
|
|
2336
2388
|
const tunnelClass = state.tunnel.up ? "status-up" : "status-down";
|
|
2337
2389
|
let attachSection;
|
|
2338
|
-
if (qrDataUrl && state.attachUrl)
|
|
2339
|
-
|
|
2390
|
+
if (qrDataUrl && state.attachUrl) {
|
|
2391
|
+
const safeAttachUrl = escapeHtml(state.attachUrl);
|
|
2392
|
+
const copyLabel = escapeHtml(s("dashboard.url.copy"));
|
|
2393
|
+
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>`;
|
|
2394
|
+
} else attachSection = `<p class="hint">${escapeHtml(s("dashboard.attach.hint"))}</p>`;
|
|
2340
2395
|
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) => {
|
|
2341
2396
|
return `<li><span class="page-id">${escapeHtml(p.id)}</span> <span class="page-url">${escapeHtml(p.url.slice(0, 120))}</span></li>`;
|
|
2342
2397
|
}).join("\n") : `<li class="empty">${escapeHtml(s("dashboard.pages.empty"))}</li>`}</ul></section>`;
|
|
@@ -2344,7 +2399,10 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
|
|
|
2344
2399
|
tunnelUp: JSON.stringify(s("dashboard.tunnel.up")),
|
|
2345
2400
|
tunnelDown: JSON.stringify(s("dashboard.tunnel.down")),
|
|
2346
2401
|
pagesEmpty: JSON.stringify(s("dashboard.pages.empty")),
|
|
2347
|
-
attachHint: JSON.stringify(s("dashboard.attach.hint"))
|
|
2402
|
+
attachHint: JSON.stringify(s("dashboard.attach.hint")),
|
|
2403
|
+
copyLabel: JSON.stringify(s("dashboard.url.copy")),
|
|
2404
|
+
copiedLabel: JSON.stringify(s("dashboard.url.copied")),
|
|
2405
|
+
dashboardSurface: true
|
|
2348
2406
|
};
|
|
2349
2407
|
const langSwitcher = buildLangSwitcher(path, params, locale, s);
|
|
2350
2408
|
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);
|
|
@@ -2358,9 +2416,24 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
|
|
|
2358
2416
|
* client side: attachUrl은 DOM에 렌더링, wssUrl은 절대 렌더링하지 않는다.
|
|
2359
2417
|
* pages === null 이면 섹션을 건드리지 않는다 (#411).
|
|
2360
2418
|
*
|
|
2419
|
+
* 두 표면(dashboard / attach) 분기:
|
|
2420
|
+
* - dashboard (dashboardSurface=true): #attach-section innerHTML 전체 교체 방식 유지.
|
|
2421
|
+
* url-box도 innerHTML 재렌더 안에 포함되어 갱신됨.
|
|
2422
|
+
* - /attach (dashboardSurface=false): #attach-section의 img src만 교체하고,
|
|
2423
|
+
* url-box는 #url-box textContent만 갱신한다. (#attach-section에 url-box가 없으므로
|
|
2424
|
+
* innerHTML 교체 시 url-box가 새로 생겨 이중 표시되는 결함을 방지 — #458 결함 수정.)
|
|
2425
|
+
*
|
|
2426
|
+
* 복사 기능: 이벤트 위임으로 document에 단일 핸들러. innerHTML 재렌더 후에도 생존.
|
|
2427
|
+
* - .url-box 클릭 또는 .copy-btn 클릭 → 현재 #url-box textContent 복사.
|
|
2428
|
+
* - clipboard: navigator.clipboard.writeText → 실패/부재 시 textarea execCommand fallback.
|
|
2429
|
+
* - 피드백: 버튼 라벨이 COPIED_LABEL로 ~1.5초 전환 후 COPY_LABEL로 복귀.
|
|
2430
|
+
*
|
|
2361
2431
|
* 문자열 인자는 빌드타임에 ko/en 테이블에서 가져와 JSON.stringify로 이미 escape됨.
|
|
2432
|
+
*
|
|
2433
|
+
* SECRET-HANDLING: URL 값을 console.log 등으로 출력하지 않는다.
|
|
2362
2434
|
*/
|
|
2363
2435
|
function buildSseScript(strings) {
|
|
2436
|
+
const isDashboard = strings.dashboardSurface;
|
|
2364
2437
|
return `<script>
|
|
2365
2438
|
// SSE — /events 구독해 상태 자동 갱신. 빌드 파이프라인 없는 인라인 스크립트.
|
|
2366
2439
|
(function () {
|
|
@@ -2368,6 +2441,64 @@ function buildSseScript(strings) {
|
|
|
2368
2441
|
var TUNNEL_DOWN = ${strings.tunnelDown};
|
|
2369
2442
|
var PAGES_EMPTY = ${strings.pagesEmpty};
|
|
2370
2443
|
var ATTACH_HINT = ${strings.attachHint};
|
|
2444
|
+
var COPY_LABEL = ${strings.copyLabel};
|
|
2445
|
+
var COPIED_LABEL = ${strings.copiedLabel};
|
|
2446
|
+
|
|
2447
|
+
// ── 클립보드 복사 헬퍼 ────────────────────────────────────────────────
|
|
2448
|
+
function copyText(text) {
|
|
2449
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2450
|
+
return navigator.clipboard.writeText(text);
|
|
2451
|
+
}
|
|
2452
|
+
// fallback: textarea + execCommand
|
|
2453
|
+
return new Promise(function (resolve, reject) {
|
|
2454
|
+
var ta = document.createElement('textarea');
|
|
2455
|
+
ta.value = text;
|
|
2456
|
+
ta.style.position = 'fixed';
|
|
2457
|
+
ta.style.opacity = '0';
|
|
2458
|
+
document.body.appendChild(ta);
|
|
2459
|
+
ta.focus();
|
|
2460
|
+
ta.select();
|
|
2461
|
+
try {
|
|
2462
|
+
document.execCommand('copy') ? resolve() : reject(new Error('execCommand failed'));
|
|
2463
|
+
} catch (err) {
|
|
2464
|
+
reject(err);
|
|
2465
|
+
} finally {
|
|
2466
|
+
document.body.removeChild(ta);
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// ── 복사 피드백 ───────────────────────────────────────────────────────
|
|
2472
|
+
var copyTimer = null;
|
|
2473
|
+
function triggerCopy() {
|
|
2474
|
+
var urlBox = document.getElementById('url-box');
|
|
2475
|
+
if (!urlBox) return;
|
|
2476
|
+
var text = urlBox.textContent || '';
|
|
2477
|
+
if (!text) return;
|
|
2478
|
+
copyText(text).then(function () {
|
|
2479
|
+
var btn = document.getElementById('copy-btn');
|
|
2480
|
+
if (btn) {
|
|
2481
|
+
btn.textContent = COPIED_LABEL;
|
|
2482
|
+
if (copyTimer) clearTimeout(copyTimer);
|
|
2483
|
+
copyTimer = setTimeout(function () {
|
|
2484
|
+
btn.textContent = COPY_LABEL;
|
|
2485
|
+
copyTimer = null;
|
|
2486
|
+
}, 1500);
|
|
2487
|
+
}
|
|
2488
|
+
}).catch(function () { /* 복사 실패 시 조용히 무시 */ });
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// ── 이벤트 위임 — document 레벨에서 단일 핸들러 (innerHTML 재렌더 후에도 생존) ──
|
|
2492
|
+
document.addEventListener('click', function (e) {
|
|
2493
|
+
var target = e.target;
|
|
2494
|
+
if (!target) return;
|
|
2495
|
+
// .copy-btn 또는 .url-box 클릭 시 복사
|
|
2496
|
+
if (target.closest && (target.closest('.copy-btn') || target.closest('.url-box'))) {
|
|
2497
|
+
triggerCopy();
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
// ── SSE 구독 ──────────────────────────────────────────────────────────
|
|
2371
2502
|
var src = new EventSource('/events');
|
|
2372
2503
|
src.onmessage = function (e) {
|
|
2373
2504
|
try {
|
|
@@ -2395,23 +2526,37 @@ function buildSseScript(strings) {
|
|
|
2395
2526
|
}
|
|
2396
2527
|
}
|
|
2397
2528
|
}
|
|
2398
|
-
// attachUrl QR
|
|
2529
|
+
// attachUrl QR + url-box 갱신
|
|
2530
|
+
// SECRET-HANDLING: URL 값을 로그로 출력하지 않는다.
|
|
2399
2531
|
var sec = document.getElementById('attach-section');
|
|
2400
2532
|
if (sec) {
|
|
2401
2533
|
if (s.attachUrl) {
|
|
2402
|
-
// QR은 서버에서 새로 렌더한 /qr.png?u= 로 img src 교체.
|
|
2403
|
-
// TOTP at= 코드는 attachUrl 안에 캡슐화 — 별도 노출 없음.
|
|
2404
|
-
// wssUrl은 절대 DOM에 렌더하지 않는다 (SECRET-HANDLING).
|
|
2405
2534
|
var encoded = encodeURIComponent(s.attachUrl);
|
|
2406
2535
|
var safeUrl = String(s.attachUrl).slice(0, 2000).replace(/[<>&"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; });
|
|
2536
|
+
${isDashboard ? `// dashboard: #attach-section innerHTML 전체 교체 (img + url-row).
|
|
2537
|
+
// url-box id="url-box" 를 포함해 복사 핸들러가 계속 동작함.
|
|
2407
2538
|
sec.innerHTML =
|
|
2408
2539
|
'<img class="qr" src="/qr.png?u=' + encoded + '" alt="attach QR" />' +
|
|
2409
|
-
'<
|
|
2540
|
+
'<div class=\\"url-row\\">' +
|
|
2541
|
+
'<p class=\\"url-box\\" id=\\"url-box\\">' + safeUrl + '</p>' +
|
|
2542
|
+
'<button class=\\"copy-btn\\" id=\\"copy-btn\\" type=\\"button\\" aria-label=\\"' + COPY_LABEL + '\\">' + COPY_LABEL + '</button>' +
|
|
2543
|
+
'</div>';` : `// /attach: img src만 교체 — url-box는 별도 #url-section에서 관리해 이중 표시 방지(#458).
|
|
2544
|
+
// QR img src 교체: img가 있으면 src만 갱신, 없으면 img 요소 생성.
|
|
2545
|
+
var img = sec.querySelector('img.qr');
|
|
2546
|
+
if (img) {
|
|
2547
|
+
img.src = '/qr.png?u=' + encoded;
|
|
2548
|
+
} else {
|
|
2549
|
+
sec.innerHTML = '<img class=\\"qr\\" src=\\"/qr.png?u=' + encoded + '\\" alt=\\"attach QR\\" />';
|
|
2550
|
+
}
|
|
2551
|
+
// url-box textContent만 갱신 (innerHTML 교체하지 않아 복사 버튼/핸들러 생존).
|
|
2552
|
+
var ub = document.getElementById('url-box');
|
|
2553
|
+
if (ub) ub.textContent = s.attachUrl;`}
|
|
2410
2554
|
} else {
|
|
2411
|
-
sec.innerHTML = '<p class
|
|
2555
|
+
${isDashboard ? `sec.innerHTML = '<p class=\\"hint\\">' + ATTACH_HINT + '</p>';` : `// /attach에서 hint가 필요한 경우는 없으나 방어 처리.
|
|
2556
|
+
sec.innerHTML = '<p class=\\"hint\\">' + ATTACH_HINT + '</p>';`}
|
|
2412
2557
|
}
|
|
2413
2558
|
}
|
|
2414
|
-
// 갱신 시각
|
|
2559
|
+
// 갱신 시각 (dashboard만 #updated 요소 있음)
|
|
2415
2560
|
var upd = document.getElementById('updated');
|
|
2416
2561
|
if (upd) upd.textContent = upd.textContent.replace(/[^ ]+$/, new Date().toISOString());
|
|
2417
2562
|
} catch (_) { /* 파싱 오류 무시 */ }
|
|
@@ -2444,7 +2589,10 @@ function buildAttachHtml(qrDataUrl, safeLabel, safeAttachUrl, locale, path = "/a
|
|
|
2444
2589
|
tunnelUp: JSON.stringify(s("dashboard.tunnel.up")),
|
|
2445
2590
|
tunnelDown: JSON.stringify(s("dashboard.tunnel.down")),
|
|
2446
2591
|
pagesEmpty: JSON.stringify(s("dashboard.pages.empty")),
|
|
2447
|
-
attachHint: JSON.stringify(s("dashboard.attach.hint"))
|
|
2592
|
+
attachHint: JSON.stringify(s("dashboard.attach.hint")),
|
|
2593
|
+
copyLabel: JSON.stringify(s("dashboard.url.copy")),
|
|
2594
|
+
copiedLabel: JSON.stringify(s("dashboard.url.copied")),
|
|
2595
|
+
dashboardSurface: false
|
|
2448
2596
|
});
|
|
2449
2597
|
return filled.replace("</body>", `${sseScript}\n</body>`);
|
|
2450
2598
|
}
|
|
@@ -3975,7 +4123,7 @@ async function readMcpSdkVersion() {
|
|
|
3975
4123
|
* some test environments that skip the build step).
|
|
3976
4124
|
*/
|
|
3977
4125
|
function readDevtoolsVersion() {
|
|
3978
|
-
return "0.1.
|
|
4126
|
+
return "0.1.67";
|
|
3979
4127
|
}
|
|
3980
4128
|
/**
|
|
3981
4129
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -4463,7 +4611,7 @@ function createDebugServer(deps) {
|
|
|
4463
4611
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
4464
4612
|
const server = new Server({
|
|
4465
4613
|
name: "ait-debug",
|
|
4466
|
-
version: "0.1.
|
|
4614
|
+
version: "0.1.67"
|
|
4467
4615
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
4468
4616
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
4469
4617
|
const conn = router.active;
|
|
@@ -6334,7 +6482,7 @@ function createDevServer(deps = {}) {
|
|
|
6334
6482
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
6335
6483
|
const server = new Server({
|
|
6336
6484
|
name: "ait-devtools",
|
|
6337
|
-
version: "0.1.
|
|
6485
|
+
version: "0.1.67"
|
|
6338
6486
|
}, { capabilities: { tools: {} } });
|
|
6339
6487
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
6340
6488
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|