@ait-co/devtools 0.1.69 → 0.1.71
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/chii-relay-BcnVJBqm.cjs +289 -0
- package/dist/chii-relay-BcnVJBqm.cjs.map +1 -0
- package/dist/chii-relay-DSVG4Ui1.js +289 -0
- package/dist/chii-relay-DSVG4Ui1.js.map +1 -0
- 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/in-app/index.d.ts.map +1 -1
- package/dist/in-app/index.js +179 -0
- package/dist/in-app/index.js.map +1 -1
- package/dist/mcp/cli.js +263 -67
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mock/index.d.ts +24 -1
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +89 -1
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +4 -2
- package/dist/panel/index.js.map +1 -1
- package/dist/{qr-http-server-DR__VNnX.cjs → qr-http-server-BIIMOcuU.cjs} +3 -1
- package/dist/qr-http-server-BIIMOcuU.cjs.map +1 -0
- package/dist/{qr-http-server-CyVQphTM.js → qr-http-server-CeEzLS3g.js} +3 -1
- package/dist/qr-http-server-CeEzLS3g.js.map +1 -0
- package/dist/{qr-http-server-DnQSQ3hC.cjs → qr-http-server-ClakYBO9.cjs} +3 -1
- package/dist/qr-http-server-ClakYBO9.cjs.map +1 -0
- package/dist/{qr-http-server-DKEca8J3.js → qr-http-server-JjGU81q7.js} +3 -1
- package/dist/qr-http-server-JjGU81q7.js.map +1 -0
- package/dist/{tunnel-BMY7KgO5.cjs → tunnel-DwVrcZ56.cjs} +2 -2
- package/dist/{tunnel-BMY7KgO5.cjs.map → tunnel-DwVrcZ56.cjs.map} +1 -1
- package/dist/{tunnel-DIN5Vvbo.js → tunnel-aIy_7nWm.js} +2 -2
- package/dist/{tunnel-DIN5Vvbo.js.map → tunnel-aIy_7nWm.js.map} +1 -1
- package/dist/unplugin/index.cjs +2 -2
- package/dist/unplugin/index.js +2 -2
- package/dist/unplugin/tunnel.cjs +1 -1
- package/dist/unplugin/tunnel.js +1 -1
- package/package.json +1 -1
- package/dist/chii-relay-BNd3G3UG.js +0 -152
- package/dist/chii-relay-BNd3G3UG.js.map +0 -1
- package/dist/chii-relay-DngjQ2_A.cjs +0 -151
- package/dist/chii-relay-DngjQ2_A.cjs.map +0 -1
- package/dist/qr-http-server-CyVQphTM.js.map +0 -1
- package/dist/qr-http-server-DKEca8J3.js.map +0 -1
- package/dist/qr-http-server-DR__VNnX.cjs.map +0 -1
- package/dist/qr-http-server-DnQSQ3hC.cjs.map +0 -1
package/dist/mcp/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { EventEmitter } from "node:events";
|
|
12
|
-
import { WebSocket } from "ws";
|
|
12
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
14
|
import { createServer } from "node:http";
|
|
15
15
|
import { spawn } from "node:child_process";
|
|
@@ -120,6 +120,47 @@ var ChiiAitSource = class {
|
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
122
|
//#endregion
|
|
123
|
+
//#region src/shared/relay-auth-close.ts
|
|
124
|
+
/**
|
|
125
|
+
* Shared constants for the relay's named TOTP-auth rejection (issue #478).
|
|
126
|
+
*
|
|
127
|
+
* Before #478 the relay rejected an unauthenticated WebSocket upgrade with a
|
|
128
|
+
* raw `HTTP/1.1 401` + `socket.destroy()`. A handshake aborted that way is
|
|
129
|
+
* indistinguishable from a network failure on the browser side — the
|
|
130
|
+
* WebSocket only ever sees close code 1006, so the phone (env-2 launcher PWA)
|
|
131
|
+
* could not tell "stale TOTP code" apart from "tunnel down" and stayed
|
|
132
|
+
* silent. The fix is accept-then-close: complete the handshake, then close
|
|
133
|
+
* with an application close code that NAMES the rejection.
|
|
134
|
+
*
|
|
135
|
+
* Three parties share this contract:
|
|
136
|
+
* - `src/mcp/chii-relay.ts` (Node) sends the close frame / HTTP error body;
|
|
137
|
+
* - `src/in-app/attach.ts` (browser) observes relay-bound WebSockets and
|
|
138
|
+
* surfaces the code to the launcher shell;
|
|
139
|
+
* - `src/mcp/chii-connection.ts` (Node daemon client) recognises the code
|
|
140
|
+
* as an auth failure on its own `/client` dial (defensive — #439's fresh
|
|
141
|
+
* code mint means it should not normally hit this).
|
|
142
|
+
*
|
|
143
|
+
* This module is intentionally dependency-free (no Node, no DOM) so it is
|
|
144
|
+
* safe to import from both the browser in-app bundle and the MCP daemon
|
|
145
|
+
* bundle.
|
|
146
|
+
*
|
|
147
|
+
* SECRET-HANDLING: these are fixed enum values. The close reason / error body
|
|
148
|
+
* must never grow to carry a secret, a TOTP code, or a host.
|
|
149
|
+
*/
|
|
150
|
+
/**
|
|
151
|
+
* WebSocket close code sent by the relay when TOTP auth is rejected.
|
|
152
|
+
*
|
|
153
|
+
* 4000–4999 is the application-reserved range (RFC 6455 §7.4.2); 4401 mirrors
|
|
154
|
+
* HTTP 401 so it reads as "unauthorized" at a glance.
|
|
155
|
+
*/
|
|
156
|
+
const RELAY_AUTH_REJECT_CLOSE_CODE = 4401;
|
|
157
|
+
/**
|
|
158
|
+
* Close reason string accompanying {@link RELAY_AUTH_REJECT_CLOSE_CODE}, and
|
|
159
|
+
* the `error` value of the relay's HTTP 401 JSON body. Enum string only —
|
|
160
|
+
* never interpolated with request data.
|
|
161
|
+
*/
|
|
162
|
+
const RELAY_AUTH_REJECT_REASON = "totp-rejected";
|
|
163
|
+
//#endregion
|
|
123
164
|
//#region src/mcp/log.ts
|
|
124
165
|
/**
|
|
125
166
|
* Allowed field keys that may pass through to a log line.
|
|
@@ -467,12 +508,15 @@ var ChiiCdpConnection = class {
|
|
|
467
508
|
await new Promise((resolve, reject) => {
|
|
468
509
|
ws.once("open", () => resolve());
|
|
469
510
|
ws.once("error", (err) => reject(err));
|
|
511
|
+
ws.once("close", (code) => {
|
|
512
|
+
if (code === 4401) reject(/* @__PURE__ */ new Error("relay 인증(TOTP)이 거부됐습니다 (close 4401). 코드가 만료됐을 수 있습니다 — 재연결 시 새 코드가 발급됩니다."));
|
|
513
|
+
});
|
|
470
514
|
});
|
|
471
515
|
this.lastCrashDetectedAt = null;
|
|
472
516
|
this.targetLastSeenAt.clear();
|
|
473
517
|
this.connectionState = "connected";
|
|
474
518
|
ws.on("message", (data) => this.handleMessage(data.toString()));
|
|
475
|
-
ws.on("close", () => this.handleDisconnect("relay WebSocket 연결이 끊겼습니다"));
|
|
519
|
+
ws.on("close", (code) => this.handleDisconnect(code === 4401 ? "relay 인증(TOTP)이 거부돼 연결이 종료됐습니다 (close 4401)" : "relay WebSocket 연결이 끊겼습니다"));
|
|
476
520
|
ws.on("error", (err) => this.handleDisconnect(`relay WebSocket 오류: ${err.message}`));
|
|
477
521
|
this.sendFireAndForget("Runtime.enable");
|
|
478
522
|
this.sendFireAndForget("Network.enable");
|
|
@@ -721,12 +765,27 @@ var ChiiCdpConnection = class {
|
|
|
721
765
|
* entries.
|
|
722
766
|
*
|
|
723
767
|
* TOTP auth (relay-side, authoritative gate):
|
|
724
|
-
* When `verifyAuth` is provided, this module
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
768
|
+
* When `verifyAuth` is provided, this module gates both inbound surfaces:
|
|
769
|
+
*
|
|
770
|
+
* - HTTP 'request': a listener registered BEFORE `chii.start({server})`.
|
|
771
|
+
* Node's `http.Server` calls listeners in registration order; the first
|
|
772
|
+
* to call `res.end()` wins. Invalid auth → 401 + CORS header + a tiny
|
|
773
|
+
* JSON body (`{"error":"totp-rejected"}`) so a cross-origin script
|
|
774
|
+
* `fetch()` probe can READ the status (issue #478). Valid auth → return
|
|
775
|
+
* without side-effect (chii's Koa handler serves it).
|
|
776
|
+
*
|
|
777
|
+
* - WS 'upgrade': after `chii.start()` has registered chii's own upgrade
|
|
778
|
+
* listener, we take over the upgrade chain (remove chii's listeners,
|
|
779
|
+
* re-dispatch manually). Invalid auth → accept-then-close: complete the
|
|
780
|
+
* handshake via a `noServer` WebSocketServer, then immediately close
|
|
781
|
+
* with code 4401 reason 'totp-rejected' (issue #478). A raw 401 +
|
|
782
|
+
* `socket.destroy()` only ever surfaced as close code 1006 in the
|
|
783
|
+
* browser — indistinguishable from a tunnel failure, which left the
|
|
784
|
+
* env-2 phone UI silent. The explicit dispatch (not listener ordering)
|
|
785
|
+
* is what keeps chii away from rejected sockets: accept-then-close
|
|
786
|
+
* leaves the socket alive, so an order-based early-return would let
|
|
787
|
+
* chii's later listener complete a SECOND handshake on the same socket
|
|
788
|
+
* — an auth bypass. Valid auth → forward to chii's captured listeners.
|
|
730
789
|
*
|
|
731
790
|
* TOTP code transports (issue #466) — two equivalent ways to carry the code:
|
|
732
791
|
* 1. Query param `at=<code>` — used by the daemon-side `/client` connection
|
|
@@ -753,6 +812,66 @@ var ChiiCdpConnection = class {
|
|
|
753
812
|
* predicate from the caller's perspective; this module only forwards pass/fail.
|
|
754
813
|
*/
|
|
755
814
|
const require$1 = createRequire(import.meta.url);
|
|
815
|
+
/**
|
|
816
|
+
* WS keepalive ping interval (ms).
|
|
817
|
+
*
|
|
818
|
+
* Cloudflare proxied connections are dropped after ~100 s of no traffic.
|
|
819
|
+
* 45 s comfortably fits inside that window and lets both the phone-target leg
|
|
820
|
+
* and the daemon-client leg survive idle CDP sessions.
|
|
821
|
+
*/
|
|
822
|
+
const DEFAULT_KEEPALIVE_INTERVAL_MS = 45e3;
|
|
823
|
+
/**
|
|
824
|
+
* Loads chii's internal WebSocketServer class and returns it together with a
|
|
825
|
+
* flag indicating whether the real class was found.
|
|
826
|
+
*
|
|
827
|
+
* Returns `null` if the internal path is not resolvable (future chii release
|
|
828
|
+
* changes the layout) — callers skip keepalive gracefully.
|
|
829
|
+
*/
|
|
830
|
+
function tryLoadChiiWssClass() {
|
|
831
|
+
try {
|
|
832
|
+
const mod = require$1("chii/server/lib/WebSocketServer");
|
|
833
|
+
if (typeof mod === "function") return mod;
|
|
834
|
+
} catch {}
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Calls `chii.start()` and returns the chii `WebSocketServer` instance that
|
|
839
|
+
* was constructed during the call.
|
|
840
|
+
*
|
|
841
|
+
* How: `chii/server/index.js`'s `start()` creates `new WebSocketServer()`
|
|
842
|
+
* where `WebSocketServer` is captured from `require('./lib/WebSocketServer')`
|
|
843
|
+
* at module load time. The class reference is stable, so we can temporarily
|
|
844
|
+
* patch `ChiiWssClass.prototype.start` — which runs *on the instance* —
|
|
845
|
+
* to record `this` before the original `start` runs.
|
|
846
|
+
*
|
|
847
|
+
* The patch is installed before `chii.start()` and removed (via `finally`)
|
|
848
|
+
* immediately after, so concurrent `startChiiRelay` calls nest correctly: each
|
|
849
|
+
* call's patch overrides the previous in the prototype chain for the duration
|
|
850
|
+
* of its own `chii.start()` call, restoring the prior descriptor on exit.
|
|
851
|
+
*
|
|
852
|
+
* If `ChiiWssClass` is null (internal path changed in a future chii release),
|
|
853
|
+
* `chii.start()` runs unpatched and the function returns null — callers skip
|
|
854
|
+
* keepalive gracefully without affecting relay correctness.
|
|
855
|
+
*/
|
|
856
|
+
async function startChiiWithCapture(chii, startOptions, ChiiWssClass) {
|
|
857
|
+
if (ChiiWssClass === null) {
|
|
858
|
+
await chii.start(startOptions);
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
let captured = null;
|
|
862
|
+
const proto = ChiiWssClass.prototype;
|
|
863
|
+
const originalStart = proto.start;
|
|
864
|
+
proto.start = function(server) {
|
|
865
|
+
captured = this;
|
|
866
|
+
return originalStart.call(this, server);
|
|
867
|
+
};
|
|
868
|
+
try {
|
|
869
|
+
await chii.start(startOptions);
|
|
870
|
+
} finally {
|
|
871
|
+
proto.start = originalStart;
|
|
872
|
+
}
|
|
873
|
+
return captured;
|
|
874
|
+
}
|
|
756
875
|
function loadChiiServer() {
|
|
757
876
|
const mod = require$1("chii");
|
|
758
877
|
if (typeof mod === "object" && mod !== null && "start" in mod && typeof mod.start === "function") return mod;
|
|
@@ -805,6 +924,7 @@ async function startChiiRelay(options = {}) {
|
|
|
805
924
|
const requestedPort = options.port ?? 0;
|
|
806
925
|
const host = options.host ?? "127.0.0.1";
|
|
807
926
|
const { verifyAuth, onAuthReject } = options;
|
|
927
|
+
const keepaliveIntervalMs = options.keepaliveIntervalMs !== void 0 ? options.keepaliveIntervalMs : DEFAULT_KEEPALIVE_INTERVAL_MS;
|
|
808
928
|
const httpServer = createServer();
|
|
809
929
|
const notifyAuthReject = (kind) => {
|
|
810
930
|
if (onAuthReject === void 0) return;
|
|
@@ -812,33 +932,41 @@ async function startChiiRelay(options = {}) {
|
|
|
812
932
|
onAuthReject({ kind });
|
|
813
933
|
} catch {}
|
|
814
934
|
};
|
|
935
|
+
if (verifyAuth) httpServer.on("request", (req, res) => {
|
|
936
|
+
const rewritten = rewriteAtPathPrefix(req.url ?? "");
|
|
937
|
+
if (rewritten === null) return;
|
|
938
|
+
req.url = rewritten;
|
|
939
|
+
if (!verifyAuth(req)) {
|
|
940
|
+
res.statusCode = 401;
|
|
941
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
942
|
+
res.setHeader("Content-Type", "application/json");
|
|
943
|
+
res.end(JSON.stringify({ error: RELAY_AUTH_REJECT_REASON }));
|
|
944
|
+
notifyAuthReject("http-request");
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
const chiiWssClass = keepaliveIntervalMs > 0 ? tryLoadChiiWssClass() : null;
|
|
948
|
+
const capturedChiiWss = await startChiiWithCapture(loadChiiServer(), {
|
|
949
|
+
server: httpServer,
|
|
950
|
+
domain: `${host}:${requestedPort}`,
|
|
951
|
+
port: requestedPort
|
|
952
|
+
}, chiiWssClass);
|
|
815
953
|
if (verifyAuth) {
|
|
816
|
-
httpServer.
|
|
954
|
+
const chiiUpgradeListeners = httpServer.listeners("upgrade");
|
|
955
|
+
httpServer.removeAllListeners("upgrade");
|
|
956
|
+
const rejectWss = new WebSocketServer({ noServer: true });
|
|
957
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
817
958
|
const rewritten = rewriteAtPathPrefix(req.url ?? "");
|
|
818
959
|
if (rewritten !== null) req.url = rewritten;
|
|
819
960
|
if (!verifyAuth(req)) {
|
|
820
|
-
|
|
821
|
-
|
|
961
|
+
rejectWss.handleUpgrade(req, socket, head, (ws) => {
|
|
962
|
+
ws.close(RELAY_AUTH_REJECT_CLOSE_CODE, RELAY_AUTH_REJECT_REASON);
|
|
963
|
+
});
|
|
822
964
|
notifyAuthReject("ws-upgrade");
|
|
823
965
|
return;
|
|
824
966
|
}
|
|
825
|
-
|
|
826
|
-
httpServer.on("request", (req, res) => {
|
|
827
|
-
const rewritten = rewriteAtPathPrefix(req.url ?? "");
|
|
828
|
-
if (rewritten === null) return;
|
|
829
|
-
req.url = rewritten;
|
|
830
|
-
if (!verifyAuth(req)) {
|
|
831
|
-
res.statusCode = 401;
|
|
832
|
-
res.end();
|
|
833
|
-
notifyAuthReject("http-request");
|
|
834
|
-
}
|
|
967
|
+
for (const listener of chiiUpgradeListeners) listener(req, socket, head);
|
|
835
968
|
});
|
|
836
969
|
}
|
|
837
|
-
await loadChiiServer().start({
|
|
838
|
-
server: httpServer,
|
|
839
|
-
domain: `${host}:${requestedPort}`,
|
|
840
|
-
port: requestedPort
|
|
841
|
-
});
|
|
842
970
|
const actualPort = await new Promise((resolve, reject) => {
|
|
843
971
|
httpServer.once("error", reject);
|
|
844
972
|
httpServer.listen(requestedPort, host, () => {
|
|
@@ -846,10 +974,21 @@ async function startChiiRelay(options = {}) {
|
|
|
846
974
|
resolve(httpServer.address().port);
|
|
847
975
|
});
|
|
848
976
|
});
|
|
977
|
+
let keepaliveHandle = null;
|
|
978
|
+
if (keepaliveIntervalMs > 0 && capturedChiiWss !== null) {
|
|
979
|
+
const chiiWss = capturedChiiWss;
|
|
980
|
+
keepaliveHandle = setInterval(() => {
|
|
981
|
+
for (const client of chiiWss._wss.clients) if (client.readyState === 1) client.ping();
|
|
982
|
+
}, keepaliveIntervalMs);
|
|
983
|
+
}
|
|
849
984
|
return {
|
|
850
985
|
port: actualPort,
|
|
851
986
|
baseUrl: `http://${host}:${actualPort}`,
|
|
852
987
|
close: () => new Promise((resolve) => {
|
|
988
|
+
if (keepaliveHandle !== null) {
|
|
989
|
+
clearInterval(keepaliveHandle);
|
|
990
|
+
keepaliveHandle = null;
|
|
991
|
+
}
|
|
853
992
|
httpServer.close(() => resolve());
|
|
854
993
|
})
|
|
855
994
|
};
|
|
@@ -1008,35 +1147,65 @@ function buildDeepLinkAttachUrl(schemeUrl, wssUrl, totpCode) {
|
|
|
1008
1147
|
//#endregion
|
|
1009
1148
|
//#region src/mcp/devtools-opener.ts
|
|
1010
1149
|
/**
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* The
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1150
|
+
* Assembles the Chii self-hosted DevTools inspector URL for a given relay
|
|
1151
|
+
* and target.
|
|
1152
|
+
*
|
|
1153
|
+
* Chii serves its own DevTools frontend at
|
|
1154
|
+
* `<relayHttpBaseUrl>/front_end/chii_app.html`. The `ws=` (plain HTTP relay)
|
|
1155
|
+
* or `wss=` (HTTPS relay) query parameter is a URL-encoded string of the form
|
|
1156
|
+
* `<relay-host>/client/<uuid>?target=<id>` (and optionally `&at=<totp>`) —
|
|
1157
|
+
* the same format used by Chii's own target list page (derived from
|
|
1158
|
+
* `chii/public/index.js`).
|
|
1159
|
+
*
|
|
1160
|
+
* The `at=` TOTP code is minted at call time via `mintTotp()`. It is valid
|
|
1161
|
+
* for the current 30-second RFC 6238 step (±1 step skew = 90 s acceptance
|
|
1162
|
+
* window). The developer must open the returned URL within that window. If
|
|
1163
|
+
* the window expires before the browser connects, the relay will reject the
|
|
1164
|
+
* WebSocket upgrade with close code 4401.
|
|
1165
|
+
*
|
|
1166
|
+
* SECRET-HANDLING: `mintTotp` returns a code, not a secret. The code is
|
|
1167
|
+
* embedded in the `wss=` parameter (inside the `at=` param) of the returned
|
|
1168
|
+
* URL. Callers MUST NOT log the returned URL to stdout (stderr is OK — it is
|
|
1169
|
+
* the intended fallback surface for the developer to copy the URL).
|
|
1170
|
+
*
|
|
1171
|
+
* @param relayHttpBaseUrl - Local HTTP base URL of the Chii relay, e.g.
|
|
1172
|
+
* `http://127.0.0.1:9100`. No trailing slash.
|
|
1173
|
+
* @param targetId - Chii target id (from `GET <relay>/targets`).
|
|
1174
|
+
* @param mintTotp - Optional function that returns a fresh 6-digit TOTP code
|
|
1175
|
+
* string. Called at most once. When omitted (TOTP disabled) no `at=` param
|
|
1176
|
+
* is added.
|
|
1028
1177
|
* @param panel - Initial panel. Defaults to `"console"`.
|
|
1029
1178
|
*
|
|
1030
1179
|
* @example
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1180
|
+
* buildChiiInspectorUrl(
|
|
1181
|
+
* 'http://127.0.0.1:9100',
|
|
1182
|
+
* 'abc123',
|
|
1183
|
+
* () => generateTotp(secret),
|
|
1184
|
+
* )
|
|
1185
|
+
* // → '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>'
|
|
1186
|
+
*/
|
|
1187
|
+
function buildChiiInspectorUrl(relayHttpBaseUrl, targetId, mintTotp, panel = "console") {
|
|
1188
|
+
let relayHost;
|
|
1189
|
+
let wsParamName;
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = new URL(relayHttpBaseUrl);
|
|
1192
|
+
relayHost = parsed.host;
|
|
1193
|
+
wsParamName = parsed.protocol === "https:" ? "wss" : "ws";
|
|
1194
|
+
} catch {
|
|
1195
|
+
relayHost = relayHttpBaseUrl.replace(/^https?:\/\//i, "");
|
|
1196
|
+
wsParamName = /^https:/i.test(relayHttpBaseUrl) ? "wss" : "ws";
|
|
1197
|
+
}
|
|
1198
|
+
const clientId = `devtools-opener-${Date.now().toString(36)}`;
|
|
1199
|
+
let wsPath = `${relayHost}/client/${clientId}?target=${encodeURIComponent(targetId)}`;
|
|
1200
|
+
if (mintTotp) {
|
|
1201
|
+
const code = mintTotp();
|
|
1202
|
+
wsPath += `&at=${encodeURIComponent(code)}`;
|
|
1203
|
+
}
|
|
1204
|
+
const params = new URLSearchParams({
|
|
1205
|
+
[wsParamName]: wsPath,
|
|
1038
1206
|
panel
|
|
1039
|
-
})
|
|
1207
|
+
});
|
|
1208
|
+
return `${relayHttpBaseUrl.replace(/\/$/, "")}/front_end/chii_app.html?${params.toString()}`;
|
|
1040
1209
|
}
|
|
1041
1210
|
/**
|
|
1042
1211
|
* Returns `true` when auto-open is **disabled** via the `AIT_AUTO_DEVTOOLS`
|
|
@@ -1106,31 +1275,45 @@ function openUrlInBrowser(url) {
|
|
|
1106
1275
|
var AutoDevtoolsOpener = class {
|
|
1107
1276
|
_opened = false;
|
|
1108
1277
|
/**
|
|
1109
|
-
* Attempts to auto-open
|
|
1278
|
+
* Attempts to auto-open Chii DevTools in the developer's browser.
|
|
1279
|
+
*
|
|
1280
|
+
* Builds a `<relay-base>/front_end/chii_app.html?wss=…` URL pointing at the
|
|
1281
|
+
* attached target. A fresh TOTP `at=` code is minted at call time so the
|
|
1282
|
+
* relay's WebSocket upgrade gate accepts the connection.
|
|
1110
1283
|
*
|
|
1111
1284
|
* No-op when any of the following conditions hold:
|
|
1112
1285
|
* 1. Already opened this session (`_opened` is true).
|
|
1113
1286
|
* 2. `AIT_AUTO_DEVTOOLS=0` opt-out is set.
|
|
1114
|
-
* 3.
|
|
1115
|
-
* 4. `
|
|
1287
|
+
* 3. `options.env` is `mock` (env 1 — F12 is already available).
|
|
1288
|
+
* 4. `options.relayHttpBaseUrl` is null/undefined/empty (relay not up yet).
|
|
1289
|
+
* 5. `options.targetId` is null/undefined/empty (no page attached yet).
|
|
1116
1290
|
*
|
|
1117
1291
|
* Always writes the DevTools URL to stderr so the developer can copy it
|
|
1118
1292
|
* if the browser open fails or the popup is blocked.
|
|
1119
1293
|
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1294
|
+
* TOTP expiry caveat: the `at=` code embedded in the URL is valid for the
|
|
1295
|
+
* current 30-second RFC 6238 step (±1 skew = 90 s). The developer must open
|
|
1296
|
+
* the URL within that window; if they miss it, reload the page or re-run
|
|
1297
|
+
* `open()` (though the once-per-session guard prevents that — restart the
|
|
1298
|
+
* MCP server if needed).
|
|
1299
|
+
*
|
|
1300
|
+
* SECRET-HANDLING: the inspector URL (written to stderr) contains the relay
|
|
1301
|
+
* host and a short-lived TOTP code. Do NOT write it to stdout or any
|
|
1302
|
+
* persistent log.
|
|
1122
1303
|
*/
|
|
1123
|
-
open(
|
|
1304
|
+
open(options) {
|
|
1124
1305
|
if (this._opened) return;
|
|
1125
1306
|
if (isAutoDevtoolsDisabled()) return;
|
|
1126
|
-
if (env === "mock") return;
|
|
1127
|
-
if (!
|
|
1307
|
+
if (options.env === "mock") return;
|
|
1308
|
+
if (!options.relayHttpBaseUrl) return;
|
|
1309
|
+
if (!options.targetId) return;
|
|
1128
1310
|
this._opened = true;
|
|
1129
|
-
const
|
|
1130
|
-
process.stderr.write(`[ait-debug] 기기가 연결됐습니다 —
|
|
1131
|
-
[ait-debug]
|
|
1311
|
+
const inspectorUrl = buildChiiInspectorUrl(options.relayHttpBaseUrl, options.targetId, options.mintTotp);
|
|
1312
|
+
process.stderr.write(`[ait-debug] 기기가 연결됐습니다 — Chii DevTools를 자동으로 엽니다.
|
|
1313
|
+
[ait-debug] DevTools URL: ${inspectorUrl}\n[ait-debug] (AIT_AUTO_DEVTOOLS=0 으로 자동 열기를 끌 수 있습니다)
|
|
1314
|
+
[ait-debug] 주의: URL의 at= 코드는 30초 창 안에서만 유효합니다.
|
|
1132
1315
|
`);
|
|
1133
|
-
if (!openUrlInBrowser(
|
|
1316
|
+
if (!openUrlInBrowser(inspectorUrl)) process.stderr.write("[ait-debug] 브라우저 자동 열기 실패 — 위 URL을 브라우저에서 직접 여세요.\n");
|
|
1134
1317
|
}
|
|
1135
1318
|
/** Returns `true` if `open()` has passed all guards and fired once. */
|
|
1136
1319
|
get opened() {
|
|
@@ -1934,6 +2117,7 @@ const en = {
|
|
|
1934
2117
|
"launcher.invalidUrl": "Enter a valid http(s):// URL.",
|
|
1935
2118
|
"launcher.debugAuthFailed": "Debug connection authentication failed",
|
|
1936
2119
|
"launcher.debugAuthFailedHint": "The QR code may have expired. Scan a fresh QR code.",
|
|
2120
|
+
"launcher.debugAuthExpiredHint": "The debug session has expired. Scan a fresh QR from the attach page on your Mac.",
|
|
1937
2121
|
"launcher.debugAuthRescanCta": "Scan a new QR",
|
|
1938
2122
|
"launcher.diagFab": "Diag",
|
|
1939
2123
|
"launcher.diagTitle": "Viewport diagnostics",
|
|
@@ -2177,6 +2361,7 @@ const tables = {
|
|
|
2177
2361
|
"launcher.invalidUrl": "올바른 http(s):// URL을 입력하세요.",
|
|
2178
2362
|
"launcher.debugAuthFailed": "디버그 연결 인증 실패",
|
|
2179
2363
|
"launcher.debugAuthFailedHint": "QR 코드가 만료되었을 수 있어요. 새 QR을 다시 스캔하세요.",
|
|
2364
|
+
"launcher.debugAuthExpiredHint": "디버그 세션이 만료됐어요. Mac의 attach 페이지에서 새 QR을 스캔하세요.",
|
|
2180
2365
|
"launcher.debugAuthRescanCta": "새 QR 스캔하기",
|
|
2181
2366
|
"launcher.diagFab": "진단",
|
|
2182
2367
|
"launcher.diagTitle": "뷰포트 진단",
|
|
@@ -4381,7 +4566,7 @@ async function readMcpSdkVersion() {
|
|
|
4381
4566
|
* some test environments that skip the build step).
|
|
4382
4567
|
*/
|
|
4383
4568
|
function readDevtoolsVersion() {
|
|
4384
|
-
return "0.1.
|
|
4569
|
+
return "0.1.71";
|
|
4385
4570
|
}
|
|
4386
4571
|
/**
|
|
4387
4572
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -4885,7 +5070,7 @@ function createDebugServer(deps) {
|
|
|
4885
5070
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
4886
5071
|
const server = new Server({
|
|
4887
5072
|
name: "ait-debug",
|
|
4888
|
-
version: "0.1.
|
|
5073
|
+
version: "0.1.71"
|
|
4889
5074
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
4890
5075
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
4891
5076
|
const conn = router.active;
|
|
@@ -5641,6 +5826,7 @@ async function bootRelayFamily(options = {}) {
|
|
|
5641
5826
|
return {
|
|
5642
5827
|
connection,
|
|
5643
5828
|
relayOrigin: "intoss-webview",
|
|
5829
|
+
relayHttpUrl: relay.baseUrl,
|
|
5644
5830
|
getTunnelStatus: () => tunnelStatus,
|
|
5645
5831
|
stop() {
|
|
5646
5832
|
tunnelProbe?.stop();
|
|
@@ -5677,6 +5863,7 @@ async function bootExternalRelayFamily(relayBaseUrl) {
|
|
|
5677
5863
|
return {
|
|
5678
5864
|
connection,
|
|
5679
5865
|
relayOrigin: "external-pwa",
|
|
5866
|
+
relayHttpUrl: relayBaseUrl,
|
|
5680
5867
|
getTunnelStatus: () => tunnelStatus,
|
|
5681
5868
|
stop() {
|
|
5682
5869
|
connection.close();
|
|
@@ -5843,7 +6030,16 @@ var DualConnectionRouter = class {
|
|
|
5843
6030
|
this.attachWatcher = startAttachWatcher(activeFamily.connection, server, this.deps.attachWatcherIntervalMs ?? 1e3, () => {
|
|
5844
6031
|
this.deps.diagnosticsCollector.recordAttach();
|
|
5845
6032
|
this.deps.onPageAttach?.();
|
|
5846
|
-
if (activeFamily.connection.kind === "relay")
|
|
6033
|
+
if (activeFamily.connection.kind === "relay") {
|
|
6034
|
+
const firstTarget = activeFamily.connection.listTargets()[0];
|
|
6035
|
+
const env = deriveEnvironment(activeFamily.connection.kind, getLiveIntent(), activeFamily.relayOrigin);
|
|
6036
|
+
this.deps.devtoolsOpener.open({
|
|
6037
|
+
relayHttpBaseUrl: activeFamily.relayHttpUrl,
|
|
6038
|
+
targetId: firstTarget?.id,
|
|
6039
|
+
mintTotp: process.env.AIT_DEBUG_TOTP_SECRET ? () => generateTotp(process.env.AIT_DEBUG_TOTP_SECRET) : void 0,
|
|
6040
|
+
env
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
5847
6043
|
});
|
|
5848
6044
|
}
|
|
5849
6045
|
/**
|
|
@@ -6761,7 +6957,7 @@ function createDevServer(deps = {}) {
|
|
|
6761
6957
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
6762
6958
|
const server = new Server({
|
|
6763
6959
|
name: "ait-devtools",
|
|
6764
|
-
version: "0.1.
|
|
6960
|
+
version: "0.1.71"
|
|
6765
6961
|
}, { capabilities: { tools: {} } });
|
|
6766
6962
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
6767
6963
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|