@ait-co/devtools 0.1.73 → 0.1.74

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.
Files changed (30) hide show
  1. package/dist/mcp/cli.js +117 -42
  2. package/dist/mcp/cli.js.map +1 -1
  3. package/dist/mcp/server.js +1 -1
  4. package/dist/panel/index.js +8 -2
  5. package/dist/panel/index.js.map +1 -1
  6. package/dist/{qr-http-server-Ditd2ndz.js → qr-http-server-Bn2ciFuC.js} +51 -6
  7. package/dist/qr-http-server-Bn2ciFuC.js.map +1 -0
  8. package/dist/{qr-http-server-0uN5jxLW.cjs → qr-http-server-BqZ8c0Bp.cjs} +51 -6
  9. package/dist/qr-http-server-BqZ8c0Bp.cjs.map +1 -0
  10. package/dist/{qr-http-server-TQG61eI4.js → qr-http-server-Cpc4jfTA.js} +51 -6
  11. package/dist/qr-http-server-Cpc4jfTA.js.map +1 -0
  12. package/dist/{qr-http-server-BTjpFS3p.cjs → qr-http-server-DNGVwI0P.cjs} +51 -6
  13. package/dist/qr-http-server-DNGVwI0P.cjs.map +1 -0
  14. package/dist/{tunnel-BxGnLAat.js → tunnel-BuymAS3O.js} +3 -2
  15. package/dist/{tunnel-BxGnLAat.js.map → tunnel-BuymAS3O.js.map} +1 -1
  16. package/dist/{tunnel-BXAWl2tI.cjs → tunnel-CAxygywQ.cjs} +3 -2
  17. package/dist/{tunnel-BXAWl2tI.cjs.map → tunnel-CAxygywQ.cjs.map} +1 -1
  18. package/dist/unplugin/index.cjs +1 -1
  19. package/dist/unplugin/index.js +1 -1
  20. package/dist/unplugin/tunnel.cjs +2 -1
  21. package/dist/unplugin/tunnel.cjs.map +1 -1
  22. package/dist/unplugin/tunnel.d.cts.map +1 -1
  23. package/dist/unplugin/tunnel.d.ts.map +1 -1
  24. package/dist/unplugin/tunnel.js +2 -1
  25. package/dist/unplugin/tunnel.js.map +1 -1
  26. package/package.json +1 -1
  27. package/dist/qr-http-server-0uN5jxLW.cjs.map +0 -1
  28. package/dist/qr-http-server-BTjpFS3p.cjs.map +0 -1
  29. package/dist/qr-http-server-Ditd2ndz.js.map +0 -1
  30. package/dist/qr-http-server-TQG61eI4.js.map +0 -1
package/dist/mcp/cli.js CHANGED
@@ -2096,6 +2096,9 @@ const en = {
2096
2096
  "dashboard.pages.empty": "No attached pages",
2097
2097
  "dashboard.url.copy": "Copy",
2098
2098
  "dashboard.url.copied": "Copied",
2099
+ "dashboard.inspector.section": "Inspector",
2100
+ "dashboard.inspector.open": "Open inspector",
2101
+ "dashboard.inspector.waiting": "Inspector URL pending — appears after a page attaches",
2099
2102
  "attach.title": "AIT Debug Session — QR Scan",
2100
2103
  "attach.deployment": "deployment: {label}",
2101
2104
  "attach.steps.section": "How to scan",
@@ -2344,6 +2347,9 @@ const tables = {
2344
2347
  "dashboard.pages.empty": "attach된 페이지 없음",
2345
2348
  "dashboard.url.copy": "복사",
2346
2349
  "dashboard.url.copied": "복사됨",
2350
+ "dashboard.inspector.section": "인스펙터",
2351
+ "dashboard.inspector.open": "인스펙터 열기",
2352
+ "dashboard.inspector.waiting": "인스펙터 URL 대기 중 (페이지 attach 후 표시됩니다)",
2347
2353
  "attach.title": "AIT 디버그 세션 — QR 스캔",
2348
2354
  "attach.deployment": "deployment: {label}",
2349
2355
  "attach.steps.section": "스캔 절차",
@@ -2479,6 +2485,14 @@ img.qr {
2479
2485
  }
2480
2486
  .copy-btn:hover { background: #30363d; }
2481
2487
  .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2488
+ .inspector-link {
2489
+ display: inline-block; margin-top: 0.5rem;
2490
+ padding: 0.45rem 1rem; border-radius: 6px;
2491
+ background: #1f6feb; color: #fff; font-size: 0.85rem; font-weight: 600;
2492
+ text-decoration: none; text-align: center;
2493
+ }
2494
+ .inspector-link:hover { background: #388bfd; }
2495
+ .inspector-hint { display: inline-block; margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.45; }
2482
2496
  ul { margin: 0; padding-left: 1.25rem; }
2483
2497
  li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
2484
2498
  li.empty { opacity: 0.4; list-style: none; padding-left: 0; }
@@ -2488,7 +2502,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0; }
2488
2502
  .lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
2489
2503
  .lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
2490
2504
  .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>`;
2505
+ </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
2506
  const attachChromeHtmlKoSandbox = `<!DOCTYPE html>
2493
2507
  <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
2508
  *, *::before, *::after { box-sizing: border-box; }
@@ -2631,6 +2645,14 @@ img.qr {
2631
2645
  }
2632
2646
  .copy-btn:hover { background: #30363d; }
2633
2647
  .hint { font-size: 0.85rem; opacity: 0.5; margin: 0.25rem 0 0; }
2648
+ .inspector-link {
2649
+ display: inline-block; margin-top: 0.5rem;
2650
+ padding: 0.45rem 1rem; border-radius: 6px;
2651
+ background: #1f6feb; color: #fff; font-size: 0.85rem; font-weight: 600;
2652
+ text-decoration: none; text-align: center;
2653
+ }
2654
+ .inspector-link:hover { background: #388bfd; }
2655
+ .inspector-hint { display: inline-block; margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.45; }
2634
2656
  ul { margin: 0; padding-left: 1.25rem; }
2635
2657
  li { margin-bottom: 0.35rem; font-size: 0.85rem; line-height: 1.5; }
2636
2658
  li.empty { opacity: 0.4; list-style: none; padding-left: 0; }
@@ -2640,7 +2662,7 @@ hr { border: none; border-top: 1px solid #21262d; width: 100%; margin: 0; }
2640
2662
  .lang-switcher { display: flex; gap: 0.5rem; font-size: 0.75rem; }
2641
2663
  .lang-switcher a { color: #58a6ff; text-decoration: none; opacity: 0.6; }
2642
2664
  .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>`;
2665
+ </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
2666
  const attachChromeHtmlEnSandbox = `<!DOCTYPE html>
2645
2667
  <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
2668
  *, *::before, *::after { box-sizing: border-box; }
@@ -2817,13 +2839,15 @@ function buildLangSwitcher(path, existingParams, locale, s) {
2817
2839
  *
2818
2840
  * 동적 파트 분류:
2819
2841
  * - "token-fill": 단일 값 교체 (__NOW__, __TUNNEL_CLASS__, __TUNNEL_STATUS__,
2820
- * __ATTACH_SECTION__)
2842
+ * __ATTACH_SECTION__, __INSPECTOR_SECTION__)
2821
2843
  * - "runtime builder": 가변 길이 구조 (__PAGES_SECTION__ — 조건부 렌더 + 가변 rows)
2822
2844
  * - "suffix": inline SSE <script> (빌드 파이프라인 없는 클라이언트 스크립트, locale
2823
2845
  * aware 문자열 포함)
2824
2846
  *
2825
2847
  * SECRET-HANDLING:
2826
2848
  * - attachUrl은 url-box 안에서만 노출 (TOTP at= 코드 캡슐 그대로).
2849
+ * - inspectorUrl은 anchor href 안에서만 노출 (TOTP at= 코드 캡슐 그대로).
2850
+ * relay host + TOTP 코드가 담길 수 있으나 대시보드 HTML은 의도된 transport.
2827
2851
  * - tunnel wssUrl은 "터널 연결됨" 상태 표시에서 UP/DOWN만 노출.
2828
2852
  * wssUrl 값 자체는 dashboard HTML에 넣지 않는다.
2829
2853
  */
@@ -2838,6 +2862,9 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
2838
2862
  const copyLabel = escapeHtml(s("dashboard.url.copy"));
2839
2863
  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
2864
  } else attachSection = `<p class="hint">${escapeHtml(s("dashboard.attach.hint"))}</p>`;
2865
+ let inspectorSection;
2866
+ 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>`;
2867
+ else inspectorSection = `<span class="inspector-hint" id="inspector-link">${escapeHtml(s("dashboard.inspector.waiting"))}</span>`;
2841
2868
  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
2869
  return `<li><span class="page-id">${escapeHtml(p.id)}</span> <span class="page-url">${escapeHtml(p.url.slice(0, 120))}</span></li>`;
2843
2870
  }).join("\n") : `<li class="empty">${escapeHtml(s("dashboard.pages.empty"))}</li>`}</ul></section>`;
@@ -2848,10 +2875,12 @@ function buildDashboardHtml(state, qrDataUrl, locale, path = "/", params = new U
2848
2875
  attachHint: JSON.stringify(s("dashboard.attach.hint")),
2849
2876
  copyLabel: JSON.stringify(s("dashboard.url.copy")),
2850
2877
  copiedLabel: JSON.stringify(s("dashboard.url.copied")),
2878
+ inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
2879
+ inspectorWaitingLabel: JSON.stringify(s("dashboard.inspector.waiting")),
2851
2880
  dashboardSurface: true
2852
2881
  };
2853
2882
  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);
2883
+ 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
2884
  const sseScript = buildSseScript(sseStrings);
2856
2885
  return filled.replace("</body>", `${sseScript}\n</body>`);
2857
2886
  }
@@ -2889,6 +2918,8 @@ function buildSseScript(strings) {
2889
2918
  var ATTACH_HINT = ${strings.attachHint};
2890
2919
  var COPY_LABEL = ${strings.copyLabel};
2891
2920
  var COPIED_LABEL = ${strings.copiedLabel};
2921
+ var INSPECTOR_OPEN_LABEL = ${strings.inspectorOpenLabel};
2922
+ var INSPECTOR_WAITING_LABEL = ${strings.inspectorWaitingLabel};
2892
2923
 
2893
2924
  // ── 클립보드 복사 헬퍼 ────────────────────────────────────────────────
2894
2925
  function copyText(text) {
@@ -3002,6 +3033,17 @@ function buildSseScript(strings) {
3002
3033
  sec.innerHTML = '<p class=\\"hint\\">' + ATTACH_HINT + '</p>';`}
3003
3034
  }
3004
3035
  }
3036
+ // 인스펙터 링크 갱신 — #inspector-link (#503).
3037
+ // SECRET-HANDLING: inspectorUrl을 console.log 등으로 출력하지 않는다.
3038
+ var insp = document.getElementById('inspector-link');
3039
+ if (insp) {
3040
+ if (s.inspectorUrl) {
3041
+ var safeInspUrl = String(s.inspectorUrl).slice(0, 2000).replace(/[<>&"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; });
3042
+ insp.outerHTML = '<a class=\\"inspector-link\\" id=\\"inspector-link\\" href=\\"' + safeInspUrl + '\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">' + INSPECTOR_OPEN_LABEL + '</a>';
3043
+ } else {
3044
+ insp.outerHTML = '<span class=\\"inspector-hint\\" id=\\"inspector-link\\">' + INSPECTOR_WAITING_LABEL + '</span>';
3045
+ }
3046
+ }
3005
3047
  // 갱신 시각 (dashboard만 #updated 요소 있음)
3006
3048
  var upd = document.getElementById('updated');
3007
3049
  if (upd) upd.textContent = upd.textContent.replace(/[^ ]+$/, new Date().toISOString());
@@ -3046,6 +3088,8 @@ function buildAttachHtml(qrDataUrl, safeLabel, safeAttachUrl, locale, path = "/a
3046
3088
  attachHint: JSON.stringify(s("dashboard.attach.hint")),
3047
3089
  copyLabel: JSON.stringify(s("dashboard.url.copy")),
3048
3090
  copiedLabel: JSON.stringify(s("dashboard.url.copied")),
3091
+ inspectorOpenLabel: JSON.stringify(s("dashboard.inspector.open")),
3092
+ inspectorWaitingLabel: JSON.stringify(s("dashboard.inspector.waiting")),
3049
3093
  dashboardSurface: false
3050
3094
  });
3051
3095
  return filled.replace("</body>", `${sseScript}\n</body>`);
@@ -3069,7 +3113,8 @@ async function startQrHttpServer(getDashboardState) {
3069
3113
  wssUrl: state.tunnel.wssUrl
3070
3114
  },
3071
3115
  pages: state.pages,
3072
- attachUrl: state.attachUrl
3116
+ attachUrl: state.attachUrl,
3117
+ inspectorUrl: state.inspectorUrl ?? null
3073
3118
  });
3074
3119
  res.write(`data: ${payload}\n\n`);
3075
3120
  }
@@ -4590,7 +4635,7 @@ async function readMcpSdkVersion() {
4590
4635
  * some test environments that skip the build step).
4591
4636
  */
4592
4637
  function readDevtoolsVersion() {
4593
- return "0.1.73";
4638
+ return "0.1.74";
4594
4639
  }
4595
4640
  /**
4596
4641
  * Derives the next recommended action from a completed diagnostics snapshot.
@@ -5094,7 +5139,7 @@ function createDebugServer(deps) {
5094
5139
  const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
5095
5140
  const server = new Server({
5096
5141
  name: "ait-debug",
5097
- version: "0.1.73"
5142
+ version: "0.1.74"
5098
5143
  }, { capabilities: { tools: { listChanged: true } } });
5099
5144
  server.setRequestHandler(ListToolsRequestSchema, () => {
5100
5145
  const conn = router.active;
@@ -6020,6 +6065,15 @@ var DualConnectionRouter = class {
6020
6065
  get activeRelayOrigin() {
6021
6066
  return this.activeFamily?.relayOrigin;
6022
6067
  }
6068
+ /**
6069
+ * Local HTTP base URL of the Chii relay for the currently-active family (#503).
6070
+ * Used by `getDashboardState` to build the inspector URL via `buildChiiInspectorUrl`.
6071
+ * Returns `undefined` when no relay family is active (local/mock mode).
6072
+ * SECRET-HANDLING: not logged — callers must not write this to stdout/logs.
6073
+ */
6074
+ get activeRelayHttpUrl() {
6075
+ return this.activeFamily?.relayHttpUrl;
6076
+ }
6023
6077
  /** Every booted family (for unified shutdown). All families are lazy (#396). */
6024
6078
  bootedFamilies() {
6025
6079
  return [...this.lazyFamilies.values()];
@@ -6147,18 +6201,25 @@ async function runDebugServer(options = {}) {
6147
6201
  return router.active;
6148
6202
  });
6149
6203
  let lastAttachParts = null;
6150
- const getDashboardState = () => ({
6151
- tunnel: {
6152
- up: router.relayTunnelStatus().up,
6153
- wssUrl: router.relayTunnelStatus().wssUrl
6154
- },
6155
- pages: router.active.listTargets().map((t) => ({
6156
- id: t.id,
6157
- url: t.url
6158
- })),
6159
- attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
6160
- mode: deriveEnvironment(router.active.kind, getLiveIntent(), router.activeRelayOrigin)
6161
- });
6204
+ const getDashboardState = () => {
6205
+ const targets = router.active.listTargets();
6206
+ const relayHttpUrl = router.activeRelayHttpUrl;
6207
+ const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
6208
+ const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
6209
+ return {
6210
+ tunnel: {
6211
+ up: router.relayTunnelStatus().up,
6212
+ wssUrl: router.relayTunnelStatus().wssUrl
6213
+ },
6214
+ pages: targets.map((t) => ({
6215
+ id: t.id,
6216
+ url: t.url
6217
+ })),
6218
+ attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
6219
+ inspectorUrl,
6220
+ mode: deriveEnvironment(router.active.kind, getLiveIntent(), router.activeRelayOrigin)
6221
+ };
6222
+ };
6162
6223
  let qrServer;
6163
6224
  try {
6164
6225
  qrServer = await startQrHttpServer(getDashboardState);
@@ -6313,17 +6374,24 @@ async function runLocalDebugServer(options = {}) {
6313
6374
  return router.active;
6314
6375
  });
6315
6376
  let lastAttachParts = null;
6316
- const getDashboardState = () => ({
6317
- tunnel: {
6318
- up: router.relayTunnelStatus().up,
6319
- wssUrl: router.relayTunnelStatus().wssUrl
6320
- },
6321
- pages: router.active.listTargets().map((t) => ({
6322
- id: t.id,
6323
- url: t.url
6324
- })),
6325
- attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null
6326
- });
6377
+ const getDashboardState = () => {
6378
+ const targets = router.active.listTargets();
6379
+ const relayHttpUrl = router.activeRelayHttpUrl;
6380
+ const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
6381
+ const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
6382
+ return {
6383
+ tunnel: {
6384
+ up: router.relayTunnelStatus().up,
6385
+ wssUrl: router.relayTunnelStatus().wssUrl
6386
+ },
6387
+ pages: targets.map((t) => ({
6388
+ id: t.id,
6389
+ url: t.url
6390
+ })),
6391
+ attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
6392
+ inspectorUrl
6393
+ };
6394
+ };
6327
6395
  let qrServer;
6328
6396
  try {
6329
6397
  qrServer = await startQrHttpServer(getDashboardState);
@@ -6462,17 +6530,24 @@ async function runMobileDebugServer(options = {}) {
6462
6530
  return router.active;
6463
6531
  });
6464
6532
  let lastAttachParts = null;
6465
- const getDashboardState = () => ({
6466
- tunnel: {
6467
- up: router.relayTunnelStatus().up,
6468
- wssUrl: router.relayTunnelStatus().wssUrl
6469
- },
6470
- pages: router.active.listTargets().map((t) => ({
6471
- id: t.id,
6472
- url: t.url
6473
- })),
6474
- attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null
6475
- });
6533
+ const getDashboardState = () => {
6534
+ const targets = router.active.listTargets();
6535
+ const relayHttpUrl = router.activeRelayHttpUrl;
6536
+ const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
6537
+ const inspectorUrl = relayHttpUrl && targets.length > 0 ? buildChiiInspectorUrl(relayHttpUrl, targets[0].id, totpSecret ? () => generateTotp(totpSecret, Date.now()) : void 0) : null;
6538
+ return {
6539
+ tunnel: {
6540
+ up: router.relayTunnelStatus().up,
6541
+ wssUrl: router.relayTunnelStatus().wssUrl
6542
+ },
6543
+ pages: targets.map((t) => ({
6544
+ id: t.id,
6545
+ url: t.url
6546
+ })),
6547
+ attachUrl: lastAttachParts ? rebuildAttachUrl(lastAttachParts) : null,
6548
+ inspectorUrl
6549
+ };
6550
+ };
6476
6551
  let qrServer;
6477
6552
  try {
6478
6553
  qrServer = await startQrHttpServer(getDashboardState);
@@ -6990,7 +7065,7 @@ function createDevServer(deps = {}) {
6990
7065
  const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
6991
7066
  const server = new Server({
6992
7067
  name: "ait-devtools",
6993
- version: "0.1.73"
7068
+ version: "0.1.74"
6994
7069
  }, { capabilities: { tools: {} } });
6995
7070
  server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
6996
7071
  server.setRequestHandler(CallToolRequestSchema, async (request) => {