@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.
Files changed (45) hide show
  1. package/dist/chii-relay-BcnVJBqm.cjs +289 -0
  2. package/dist/chii-relay-BcnVJBqm.cjs.map +1 -0
  3. package/dist/chii-relay-DSVG4Ui1.js +289 -0
  4. package/dist/chii-relay-DSVG4Ui1.js.map +1 -0
  5. package/dist/devtools-opener-BbUXBzgA.js.map +1 -1
  6. package/dist/devtools-opener-Bp671YXu.cjs.map +1 -1
  7. package/dist/devtools-opener-D84kZFtR.js.map +1 -1
  8. package/dist/devtools-opener-h6A-UjzC.cjs.map +1 -1
  9. package/dist/in-app/index.d.ts.map +1 -1
  10. package/dist/in-app/index.js +179 -0
  11. package/dist/in-app/index.js.map +1 -1
  12. package/dist/mcp/cli.js +263 -67
  13. package/dist/mcp/cli.js.map +1 -1
  14. package/dist/mcp/server.js +1 -1
  15. package/dist/mock/index.d.ts +24 -1
  16. package/dist/mock/index.d.ts.map +1 -1
  17. package/dist/mock/index.js +89 -1
  18. package/dist/mock/index.js.map +1 -1
  19. package/dist/panel/index.js +4 -2
  20. package/dist/panel/index.js.map +1 -1
  21. package/dist/{qr-http-server-DR__VNnX.cjs → qr-http-server-BIIMOcuU.cjs} +3 -1
  22. package/dist/qr-http-server-BIIMOcuU.cjs.map +1 -0
  23. package/dist/{qr-http-server-CyVQphTM.js → qr-http-server-CeEzLS3g.js} +3 -1
  24. package/dist/qr-http-server-CeEzLS3g.js.map +1 -0
  25. package/dist/{qr-http-server-DnQSQ3hC.cjs → qr-http-server-ClakYBO9.cjs} +3 -1
  26. package/dist/qr-http-server-ClakYBO9.cjs.map +1 -0
  27. package/dist/{qr-http-server-DKEca8J3.js → qr-http-server-JjGU81q7.js} +3 -1
  28. package/dist/qr-http-server-JjGU81q7.js.map +1 -0
  29. package/dist/{tunnel-BMY7KgO5.cjs → tunnel-DwVrcZ56.cjs} +2 -2
  30. package/dist/{tunnel-BMY7KgO5.cjs.map → tunnel-DwVrcZ56.cjs.map} +1 -1
  31. package/dist/{tunnel-DIN5Vvbo.js → tunnel-aIy_7nWm.js} +2 -2
  32. package/dist/{tunnel-DIN5Vvbo.js.map → tunnel-aIy_7nWm.js.map} +1 -1
  33. package/dist/unplugin/index.cjs +2 -2
  34. package/dist/unplugin/index.js +2 -2
  35. package/dist/unplugin/tunnel.cjs +1 -1
  36. package/dist/unplugin/tunnel.js +1 -1
  37. package/package.json +1 -1
  38. package/dist/chii-relay-BNd3G3UG.js +0 -152
  39. package/dist/chii-relay-BNd3G3UG.js.map +0 -1
  40. package/dist/chii-relay-DngjQ2_A.cjs +0 -151
  41. package/dist/chii-relay-DngjQ2_A.cjs.map +0 -1
  42. package/dist/qr-http-server-CyVQphTM.js.map +0 -1
  43. package/dist/qr-http-server-DKEca8J3.js.map +0 -1
  44. package/dist/qr-http-server-DR__VNnX.cjs.map +0 -1
  45. package/dist/qr-http-server-DnQSQ3hC.cjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ait-co/devtools",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "Development tools for Apps in Toss mini-apps — mock SDK, floating devtools panel, and universal bundler plugin",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,152 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { createServer } from "node:http";
3
- //#region src/mcp/chii-relay.ts
4
- /**
5
- * Boots the local Chii relay server.
6
- *
7
- * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome
8
- * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.
9
- * The relay accepts a `target` websocket from the phone's injected `target.js`
10
- * and `client` websockets from CDP frontends (our MCP connection).
11
- *
12
- * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app
13
- * entries.
14
- *
15
- * TOTP auth (relay-side, authoritative gate):
16
- * When `verifyAuth` is provided, this module registers HTTP 'upgrade' and
17
- * 'request' listeners on the server BEFORE calling `chii.start({server})`.
18
- * Node's `http.Server` calls listeners in registration order; the first to
19
- * call `socket.destroy()` (upgrade) or `res.end()` (request) wins. Invalid
20
- * auth → 401 + destroy (chii never sees the connection). Valid auth →
21
- * return without side-effect (chii handles it).
22
- *
23
- * TOTP code transports (issue #466) — two equivalent ways to carry the code:
24
- * 1. Query param `at=<code>` — used by the daemon-side `/client` connection
25
- * (`chii-connection.ts` appends it; it holds the secret).
26
- * 2. Path prefix `/at/<code>/…` — used by the phone-side target. Chii's
27
- * stock `target.js` derives its WS endpoint from the script `src`
28
- * (`scriptEl.src.replace('target.js','')`), so the only way for the
29
- * phone to carry a code is to embed it in the script URL path. The
30
- * in-app attach injects `https://<host>/at/<code>/target.js`; both the
31
- * script fetch and the derived `wss://<host>/at/<code>/target/<id>` WS
32
- * dial then carry the prefix. The listeners below rewrite the prefix
33
- * into the query form (`rewriteAtPathPrefix`) and MUTATE `req.url`
34
- * before chii's own handlers (registered later) parse it — chii only
35
- * ever sees the stripped URL.
36
- *
37
- * Threat model: "URL leak" — someone obtains the tunnel URL (Slack paste, QR
38
- * screenshot, shoulder-surfing) but does not have the shared TOTP secret.
39
- * Rotating 6-digit code makes the URL stale after 30 s.
40
- * A determined attacker who extracts the secret from the dogfood bundle can
41
- * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).
42
- *
43
- * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear
44
- * in any log, error message, or process output. `verifyAuth` is a black-box
45
- * predicate from the caller's perspective; this module only forwards pass/fail.
46
- */
47
- const require = createRequire(import.meta.url);
48
- function loadChiiServer() {
49
- const mod = require("chii");
50
- if (typeof mod === "object" && mod !== null && "start" in mod && typeof mod.start === "function") return mod;
51
- throw new Error("chii server module did not expose start()");
52
- }
53
- /**
54
- * Rewrites a `/at/<code>/…` path-prefixed request URL into the equivalent
55
- * query-based form, e.g.:
56
- *
57
- * `/at/123456/target.js` → `/target.js?at=123456`
58
- * `/at/123456/target/x?url=u` → `/target/x?url=u&at=123456`
59
- * `/at/123456/` → `/?at=123456`
60
- *
61
- * Returns `null` when the URL does not carry the prefix (including an empty
62
- * code segment) — callers fall back to the unmodified URL and the existing
63
- * query-based auth path.
64
- *
65
- * Pure string surgery — this function knows nothing about secrets or code
66
- * validity; verification stays inside the caller-provided `verifyAuth`
67
- * predicate (which parses the query). The raw path segment is appended
68
- * verbatim to the query: both path segments and query values are
69
- * percent-decoded exactly once by their consumers, so no re-encoding is
70
- * needed (TOTP codes are 6 digits and never percent-encoded in practice).
71
- */
72
- function rewriteAtPathPrefix(rawUrl) {
73
- const match = /^\/at\/([^/?]+)(\/[^?]*)?(\?.*)?$/.exec(rawUrl);
74
- if (match === null) return null;
75
- const code = match[1];
76
- const path = match[2] === void 0 || match[2] === "" ? "/" : match[2];
77
- const query = match[3] ?? "";
78
- return `${path}${query}${query === "" ? "?" : "&"}at=${code}`;
79
- }
80
- /**
81
- * Starts the Chii relay and resolves once listening.
82
- *
83
- * Default port is 0 (OS-assigned). With port 0 the OS picks a free ephemeral
84
- * port on every start, so a stale cloudflared orphan holding any particular
85
- * port cannot cause EADDRINUSE. The resolved `ChiiRelay.port` and `baseUrl`
86
- * always reflect the actual bound port.
87
- *
88
- * chii.start() is called with `server` (our pre-created httpServer) BEFORE
89
- * httpServer.listen(). This is intentional: chii attaches its Koa handler and
90
- * WS upgrade listener to the server object, but the actual TCP bind is
91
- * performed by our httpServer.listen() call below. The `port`/`domain` values
92
- * passed to chii.start() are used for display/banner purposes inside chii and
93
- * do not affect which port the server binds. The connection path (clients
94
- * connecting to `relay.baseUrl`) always uses the post-listen confirmed port.
95
- */
96
- async function startChiiRelay(options = {}) {
97
- const requestedPort = options.port ?? 0;
98
- const host = options.host ?? "127.0.0.1";
99
- const { verifyAuth, onAuthReject } = options;
100
- const httpServer = createServer();
101
- const notifyAuthReject = (kind) => {
102
- if (onAuthReject === void 0) return;
103
- try {
104
- onAuthReject({ kind });
105
- } catch {}
106
- };
107
- if (verifyAuth) {
108
- httpServer.on("upgrade", (req, socket) => {
109
- const rewritten = rewriteAtPathPrefix(req.url ?? "");
110
- if (rewritten !== null) req.url = rewritten;
111
- if (!verifyAuth(req)) {
112
- socket.write("HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n");
113
- socket.destroy();
114
- notifyAuthReject("ws-upgrade");
115
- return;
116
- }
117
- });
118
- httpServer.on("request", (req, res) => {
119
- const rewritten = rewriteAtPathPrefix(req.url ?? "");
120
- if (rewritten === null) return;
121
- req.url = rewritten;
122
- if (!verifyAuth(req)) {
123
- res.statusCode = 401;
124
- res.end();
125
- notifyAuthReject("http-request");
126
- }
127
- });
128
- }
129
- await loadChiiServer().start({
130
- server: httpServer,
131
- domain: `${host}:${requestedPort}`,
132
- port: requestedPort
133
- });
134
- const actualPort = await new Promise((resolve, reject) => {
135
- httpServer.once("error", reject);
136
- httpServer.listen(requestedPort, host, () => {
137
- httpServer.off("error", reject);
138
- resolve(httpServer.address().port);
139
- });
140
- });
141
- return {
142
- port: actualPort,
143
- baseUrl: `http://${host}:${actualPort}`,
144
- close: () => new Promise((resolve) => {
145
- httpServer.close(() => resolve());
146
- })
147
- };
148
- }
149
- //#endregion
150
- export { startChiiRelay };
151
-
152
- //# sourceMappingURL=chii-relay-BNd3G3UG.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chii-relay-BNd3G3UG.js","names":[],"sources":["../src/mcp/chii-relay.ts"],"sourcesContent":["/**\n * Boots the local Chii relay server.\n *\n * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome\n * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.\n * The relay accepts a `target` websocket from the phone's injected `target.js`\n * and `client` websockets from CDP frontends (our MCP connection).\n *\n * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app\n * entries.\n *\n * TOTP auth (relay-side, authoritative gate):\n * When `verifyAuth` is provided, this module registers HTTP 'upgrade' and\n * 'request' listeners on the server BEFORE calling `chii.start({server})`.\n * Node's `http.Server` calls listeners in registration order; the first to\n * call `socket.destroy()` (upgrade) or `res.end()` (request) wins. Invalid\n * auth → 401 + destroy (chii never sees the connection). Valid auth →\n * return without side-effect (chii handles it).\n *\n * TOTP code transports (issue #466) — two equivalent ways to carry the code:\n * 1. Query param `at=<code>` — used by the daemon-side `/client` connection\n * (`chii-connection.ts` appends it; it holds the secret).\n * 2. Path prefix `/at/<code>/…` — used by the phone-side target. Chii's\n * stock `target.js` derives its WS endpoint from the script `src`\n * (`scriptEl.src.replace('target.js','')`), so the only way for the\n * phone to carry a code is to embed it in the script URL path. The\n * in-app attach injects `https://<host>/at/<code>/target.js`; both the\n * script fetch and the derived `wss://<host>/at/<code>/target/<id>` WS\n * dial then carry the prefix. The listeners below rewrite the prefix\n * into the query form (`rewriteAtPathPrefix`) and MUTATE `req.url`\n * before chii's own handlers (registered later) parse it — chii only\n * ever sees the stripped URL.\n *\n * Threat model: \"URL leak\" — someone obtains the tunnel URL (Slack paste, QR\n * screenshot, shoulder-surfing) but does not have the shared TOTP secret.\n * Rotating 6-digit code makes the URL stale after 30 s.\n * A determined attacker who extracts the secret from the dogfood bundle can\n * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).\n *\n * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear\n * in any log, error message, or process output. `verifyAuth` is a black-box\n * predicate from the caller's perspective; this module only forwards pass/fail.\n */\n\nimport { createServer, type IncomingMessage, type Server } from 'node:http';\nimport { createRequire } from 'node:module';\nimport type { AddressInfo } from 'node:net';\nimport type { Duplex } from 'node:stream';\n\nconst require = createRequire(import.meta.url);\n\n/** `chii/server` is CommonJS and shipped without TypeScript types. */\ninterface ChiiServerModule {\n start(options: {\n port?: number;\n host?: string;\n domain?: string;\n server?: Server;\n basePath?: string;\n }): Promise<void>;\n}\n\nfunction loadChiiServer(): ChiiServerModule {\n // `chii`'s package `main` is `./server/index.js`, exposing `{ start }`.\n const mod: unknown = require('chii');\n if (\n typeof mod === 'object' &&\n mod !== null &&\n 'start' in mod &&\n typeof (mod as { start: unknown }).start === 'function'\n ) {\n return mod as ChiiServerModule;\n }\n throw new Error('chii server module did not expose start()');\n}\n\nexport interface ChiiRelay {\n port: number;\n /** Base URL for the relay HTTP/WS server, e.g. `http://127.0.0.1:54321`. */\n baseUrl: string;\n close(): Promise<void>;\n}\n\n/**\n * Secret-free metadata about a single auth rejection (issue #467).\n *\n * SECRET-HANDLING: this event carries ONLY the surface kind. It must never\n * grow fields for `req.url`, query strings, codes, or secrets — observers\n * (diagnostics counters, console hints) only need \"a rejection happened\".\n */\nexport interface RelayAuthRejectEvent {\n /** Which inbound surface was rejected. */\n kind: 'ws-upgrade' | 'http-request';\n}\n\n/**\n * Rewrites a `/at/<code>/…` path-prefixed request URL into the equivalent\n * query-based form, e.g.:\n *\n * `/at/123456/target.js` → `/target.js?at=123456`\n * `/at/123456/target/x?url=u` → `/target/x?url=u&at=123456`\n * `/at/123456/` → `/?at=123456`\n *\n * Returns `null` when the URL does not carry the prefix (including an empty\n * code segment) — callers fall back to the unmodified URL and the existing\n * query-based auth path.\n *\n * Pure string surgery — this function knows nothing about secrets or code\n * validity; verification stays inside the caller-provided `verifyAuth`\n * predicate (which parses the query). The raw path segment is appended\n * verbatim to the query: both path segments and query values are\n * percent-decoded exactly once by their consumers, so no re-encoding is\n * needed (TOTP codes are 6 digits and never percent-encoded in practice).\n */\nexport function rewriteAtPathPrefix(rawUrl: string): string | null {\n const match = /^\\/at\\/([^/?]+)(\\/[^?]*)?(\\?.*)?$/.exec(rawUrl);\n if (match === null) return null;\n const code = match[1];\n const path = match[2] === undefined || match[2] === '' ? '/' : match[2];\n const query = match[3] ?? '';\n const separator = query === '' ? '?' : '&';\n return `${path}${query}${separator}at=${code}`;\n}\n\nexport interface StartChiiRelayOptions {\n /**\n * Local port for the relay. Default 0 (OS-assigned ephemeral port).\n *\n * Using 0 means the OS picks a free port — this is the safe default because\n * a stale cloudflared child process (PPID 1, orphaned after SIGKILL) may still\n * be holding a fixed port. A fixed port causes EADDRINUSE on the next startup,\n * which makes the MCP handshake fail with -32000. With port 0 the new relay\n * always gets a fresh port, making any orphaned process harmless.\n *\n * Pass an explicit number to restore fixed-port behaviour (backwards-compatible).\n */\n port?: number;\n /** Bind host. Default 127.0.0.1 (tunnel reaches it locally). */\n host?: string;\n /**\n * Optional auth predicate for WebSocket upgrade requests.\n *\n * When provided, every inbound WebSocket upgrade is checked by calling\n * `verifyAuth(req)` before Chii processes it. Return `true` to allow the\n * upgrade; return `false` to reject with HTTP 401 and destroy the socket.\n *\n * The predicate MUST NOT log the secret or any TOTP code — it is a black-box\n * from this module's perspective.\n *\n * @param req - The raw HTTP `IncomingMessage` from the upgrade handshake.\n * Inspect `req.url` for query parameters (e.g. `at=<code>`). Path-prefixed\n * URLs (`/at/<code>/…`, the phone-target transport — issue #466) are\n * rewritten into the query form BEFORE this predicate runs, so a\n * query-only predicate covers both transports.\n * @returns `true` if the upgrade is authorised, `false` to reject.\n */\n verifyAuth?: (req: IncomingMessage) => boolean;\n /**\n * Secret-free observability callback fired on every auth rejection\n * (issue #467). Only meaningful together with `verifyAuth`.\n *\n * SECRET-HANDLING: the event carries ONLY the rejection kind — never\n * `req.url`, query strings, TOTP codes, or the secret. Implementations must\n * keep it that way (e.g. increment a counter + timestamp). Exceptions thrown\n * by the callback are swallowed so observability can never break the gate.\n */\n onAuthReject?: (event: RelayAuthRejectEvent) => void;\n}\n\n/**\n * Starts the Chii relay and resolves once listening.\n *\n * Default port is 0 (OS-assigned). With port 0 the OS picks a free ephemeral\n * port on every start, so a stale cloudflared orphan holding any particular\n * port cannot cause EADDRINUSE. The resolved `ChiiRelay.port` and `baseUrl`\n * always reflect the actual bound port.\n *\n * chii.start() is called with `server` (our pre-created httpServer) BEFORE\n * httpServer.listen(). This is intentional: chii attaches its Koa handler and\n * WS upgrade listener to the server object, but the actual TCP bind is\n * performed by our httpServer.listen() call below. The `port`/`domain` values\n * passed to chii.start() are used for display/banner purposes inside chii and\n * do not affect which port the server binds. The connection path (clients\n * connecting to `relay.baseUrl`) always uses the post-listen confirmed port.\n */\nexport async function startChiiRelay(options: StartChiiRelayOptions = {}): Promise<ChiiRelay> {\n const requestedPort = options.port ?? 0;\n const host = options.host ?? '127.0.0.1';\n const { verifyAuth, onAuthReject } = options;\n\n const httpServer = createServer();\n\n // Secret-free observability hook (issue #467). Swallow callback exceptions —\n // a broken observer must never turn into an open gate or a crashed relay.\n const notifyAuthReject = (kind: RelayAuthRejectEvent['kind']): void => {\n if (onAuthReject === undefined) return;\n try {\n onAuthReject({ kind });\n } catch {\n // Ignore — observability is best-effort.\n }\n };\n\n // Register our auth listeners BEFORE chii.start() so they fire first.\n // Node's http.Server emits 'upgrade'/'request' to all listeners in\n // registration order; the first to destroy() the socket (upgrade) or end()\n // the response (request) wins. Valid requests return without side-effect so\n // chii's own handlers take over normally — and because listeners run\n // synchronously in order, mutating `req.url` here (path-prefix strip,\n // issue #466) means chii's later-registered handlers only ever see the\n // stripped URL.\n //\n // We only register when verifyAuth is provided so the no-auth path is\n // zero-overhead for tests and local-only dev sessions. (The phone-side\n // `/at/<code>/` prefix only ever appears when TOTP is armed — the launcher\n // QR carries the `at` code — so the no-auth path never needs the strip.)\n if (verifyAuth) {\n httpServer.on('upgrade', (req: IncomingMessage, socket: Duplex) => {\n // Phone-target transport (issue #466): normalise a `/at/<code>/…` path\n // prefix into the query form before verification, and strip it from the\n // URL chii will see. No-prefix URLs pass through untouched (daemon\n // client query transport — back-compat).\n const rewritten = rewriteAtPathPrefix(req.url ?? '');\n if (rewritten !== null) {\n req.url = rewritten;\n }\n if (!verifyAuth(req)) {\n // Reject: send a minimal HTTP 401 response and close the socket.\n // We do NOT log req.url or any auth param here to avoid leaking codes.\n socket.write('HTTP/1.1 401 Unauthorized\\r\\nContent-Length: 0\\r\\n\\r\\n');\n socket.destroy();\n notifyAuthReject('ws-upgrade');\n // Early return — chii's handler is NOT called for this socket.\n return;\n }\n // Auth passed: no-op. Chii's upgrade listener (registered below by\n // chii.start) will handle the rest.\n });\n\n // Plain HTTP requests: only the path-prefixed form is ours — the phone\n // fetches `target.js` via `https://<host>/at/<code>/target.js` (issue\n // #466), which must be verified + stripped so chii's Koa static handler\n // serves `/target.js`. Non-prefixed requests keep today's behaviour\n // (ungated pass-through to chii).\n httpServer.on('request', (req, res) => {\n const rewritten = rewriteAtPathPrefix(req.url ?? '');\n if (rewritten === null) return;\n req.url = rewritten;\n if (!verifyAuth(req)) {\n // We do NOT log req.url or any auth param here to avoid leaking codes.\n res.statusCode = 401;\n res.end();\n notifyAuthReject('http-request');\n }\n // Auth passed: no-op — chii's Koa 'request' listener (registered below\n // by chii.start) serves the rewritten URL. (Koa skips writing when an\n // earlier listener already ended the response, so the 401 path is safe\n // even though Koa still runs.)\n });\n }\n\n const chii = loadChiiServer();\n // Passing an existing `server` makes chii attach its Koa handler + WS upgrade\n // to our HTTP server rather than creating its own listener.\n // Note: port/domain here are display-only inside chii — the TCP bind is ours.\n await chii.start({ server: httpServer, domain: `${host}:${requestedPort}`, port: requestedPort });\n\n const actualPort = await new Promise<number>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(requestedPort, host, () => {\n httpServer.off('error', reject);\n // httpServer.address() is non-null immediately after the listen callback.\n const addr = httpServer.address() as AddressInfo;\n resolve(addr.port);\n });\n });\n\n return {\n port: actualPort,\n baseUrl: `http://${host}:${actualPort}`,\n close: () =>\n new Promise<void>((resolve) => {\n httpServer.close(() => resolve());\n }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAa9C,SAAS,iBAAmC;CAE1C,MAAM,MAAe,QAAQ,OAAO;AACpC,KACE,OAAO,QAAQ,YACf,QAAQ,QACR,WAAW,OACX,OAAQ,IAA2B,UAAU,WAE7C,QAAO;AAET,OAAM,IAAI,MAAM,4CAA4C;;;;;;;;;;;;;;;;;;;;;AAyC9D,SAAgB,oBAAoB,QAA+B;CACjE,MAAM,QAAQ,oCAAoC,KAAK,OAAO;AAC9D,KAAI,UAAU,KAAM,QAAO;CAC3B,MAAM,OAAO,MAAM;CACnB,MAAM,OAAO,MAAM,OAAO,KAAA,KAAa,MAAM,OAAO,KAAK,MAAM,MAAM;CACrE,MAAM,QAAQ,MAAM,MAAM;AAE1B,QAAO,GAAG,OAAO,QADC,UAAU,KAAK,MAAM,IACJ,KAAK;;;;;;;;;;;;;;;;;;AAgE1C,eAAsB,eAAe,UAAiC,EAAE,EAAsB;CAC5F,MAAM,gBAAgB,QAAQ,QAAQ;CACtC,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,EAAE,YAAY,iBAAiB;CAErC,MAAM,aAAa,cAAc;CAIjC,MAAM,oBAAoB,SAA6C;AACrE,MAAI,iBAAiB,KAAA,EAAW;AAChC,MAAI;AACF,gBAAa,EAAE,MAAM,CAAC;UAChB;;AAkBV,KAAI,YAAY;AACd,aAAW,GAAG,YAAY,KAAsB,WAAmB;GAKjE,MAAM,YAAY,oBAAoB,IAAI,OAAO,GAAG;AACpD,OAAI,cAAc,KAChB,KAAI,MAAM;AAEZ,OAAI,CAAC,WAAW,IAAI,EAAE;AAGpB,WAAO,MAAM,yDAAyD;AACtE,WAAO,SAAS;AAChB,qBAAiB,aAAa;AAE9B;;IAIF;AAOF,aAAW,GAAG,YAAY,KAAK,QAAQ;GACrC,MAAM,YAAY,oBAAoB,IAAI,OAAO,GAAG;AACpD,OAAI,cAAc,KAAM;AACxB,OAAI,MAAM;AACV,OAAI,CAAC,WAAW,IAAI,EAAE;AAEpB,QAAI,aAAa;AACjB,QAAI,KAAK;AACT,qBAAiB,eAAe;;IAMlC;;AAOJ,OAJa,gBAAgB,CAIlB,MAAM;EAAE,QAAQ;EAAY,QAAQ,GAAG,KAAK,GAAG;EAAiB,MAAM;EAAe,CAAC;CAEjG,MAAM,aAAa,MAAM,IAAI,SAAiB,SAAS,WAAW;AAChE,aAAW,KAAK,SAAS,OAAO;AAChC,aAAW,OAAO,eAAe,YAAY;AAC3C,cAAW,IAAI,SAAS,OAAO;AAG/B,WADa,WAAW,SAAS,CACpB,KAAK;IAClB;GACF;AAEF,QAAO;EACL,MAAM;EACN,SAAS,UAAU,KAAK,GAAG;EAC3B,aACE,IAAI,SAAe,YAAY;AAC7B,cAAW,YAAY,SAAS,CAAC;IACjC;EACL"}
@@ -1,151 +0,0 @@
1
- let node_http = require("node:http");
2
- //#region src/mcp/chii-relay.ts
3
- /**
4
- * Boots the local Chii relay server.
5
- *
6
- * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome
7
- * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.
8
- * The relay accepts a `target` websocket from the phone's injected `target.js`
9
- * and `client` websockets from CDP frontends (our MCP connection).
10
- *
11
- * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app
12
- * entries.
13
- *
14
- * TOTP auth (relay-side, authoritative gate):
15
- * When `verifyAuth` is provided, this module registers HTTP 'upgrade' and
16
- * 'request' listeners on the server BEFORE calling `chii.start({server})`.
17
- * Node's `http.Server` calls listeners in registration order; the first to
18
- * call `socket.destroy()` (upgrade) or `res.end()` (request) wins. Invalid
19
- * auth → 401 + destroy (chii never sees the connection). Valid auth →
20
- * return without side-effect (chii handles it).
21
- *
22
- * TOTP code transports (issue #466) — two equivalent ways to carry the code:
23
- * 1. Query param `at=<code>` — used by the daemon-side `/client` connection
24
- * (`chii-connection.ts` appends it; it holds the secret).
25
- * 2. Path prefix `/at/<code>/…` — used by the phone-side target. Chii's
26
- * stock `target.js` derives its WS endpoint from the script `src`
27
- * (`scriptEl.src.replace('target.js','')`), so the only way for the
28
- * phone to carry a code is to embed it in the script URL path. The
29
- * in-app attach injects `https://<host>/at/<code>/target.js`; both the
30
- * script fetch and the derived `wss://<host>/at/<code>/target/<id>` WS
31
- * dial then carry the prefix. The listeners below rewrite the prefix
32
- * into the query form (`rewriteAtPathPrefix`) and MUTATE `req.url`
33
- * before chii's own handlers (registered later) parse it — chii only
34
- * ever sees the stripped URL.
35
- *
36
- * Threat model: "URL leak" — someone obtains the tunnel URL (Slack paste, QR
37
- * screenshot, shoulder-surfing) but does not have the shared TOTP secret.
38
- * Rotating 6-digit code makes the URL stale after 30 s.
39
- * A determined attacker who extracts the secret from the dogfood bundle can
40
- * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).
41
- *
42
- * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear
43
- * in any log, error message, or process output. `verifyAuth` is a black-box
44
- * predicate from the caller's perspective; this module only forwards pass/fail.
45
- */
46
- const require$1 = (0, require("node:module").createRequire)(require("url").pathToFileURL(__filename).href);
47
- function loadChiiServer() {
48
- const mod = require$1("chii");
49
- if (typeof mod === "object" && mod !== null && "start" in mod && typeof mod.start === "function") return mod;
50
- throw new Error("chii server module did not expose start()");
51
- }
52
- /**
53
- * Rewrites a `/at/<code>/…` path-prefixed request URL into the equivalent
54
- * query-based form, e.g.:
55
- *
56
- * `/at/123456/target.js` → `/target.js?at=123456`
57
- * `/at/123456/target/x?url=u` → `/target/x?url=u&at=123456`
58
- * `/at/123456/` → `/?at=123456`
59
- *
60
- * Returns `null` when the URL does not carry the prefix (including an empty
61
- * code segment) — callers fall back to the unmodified URL and the existing
62
- * query-based auth path.
63
- *
64
- * Pure string surgery — this function knows nothing about secrets or code
65
- * validity; verification stays inside the caller-provided `verifyAuth`
66
- * predicate (which parses the query). The raw path segment is appended
67
- * verbatim to the query: both path segments and query values are
68
- * percent-decoded exactly once by their consumers, so no re-encoding is
69
- * needed (TOTP codes are 6 digits and never percent-encoded in practice).
70
- */
71
- function rewriteAtPathPrefix(rawUrl) {
72
- const match = /^\/at\/([^/?]+)(\/[^?]*)?(\?.*)?$/.exec(rawUrl);
73
- if (match === null) return null;
74
- const code = match[1];
75
- const path = match[2] === void 0 || match[2] === "" ? "/" : match[2];
76
- const query = match[3] ?? "";
77
- return `${path}${query}${query === "" ? "?" : "&"}at=${code}`;
78
- }
79
- /**
80
- * Starts the Chii relay and resolves once listening.
81
- *
82
- * Default port is 0 (OS-assigned). With port 0 the OS picks a free ephemeral
83
- * port on every start, so a stale cloudflared orphan holding any particular
84
- * port cannot cause EADDRINUSE. The resolved `ChiiRelay.port` and `baseUrl`
85
- * always reflect the actual bound port.
86
- *
87
- * chii.start() is called with `server` (our pre-created httpServer) BEFORE
88
- * httpServer.listen(). This is intentional: chii attaches its Koa handler and
89
- * WS upgrade listener to the server object, but the actual TCP bind is
90
- * performed by our httpServer.listen() call below. The `port`/`domain` values
91
- * passed to chii.start() are used for display/banner purposes inside chii and
92
- * do not affect which port the server binds. The connection path (clients
93
- * connecting to `relay.baseUrl`) always uses the post-listen confirmed port.
94
- */
95
- async function startChiiRelay(options = {}) {
96
- const requestedPort = options.port ?? 0;
97
- const host = options.host ?? "127.0.0.1";
98
- const { verifyAuth, onAuthReject } = options;
99
- const httpServer = (0, node_http.createServer)();
100
- const notifyAuthReject = (kind) => {
101
- if (onAuthReject === void 0) return;
102
- try {
103
- onAuthReject({ kind });
104
- } catch {}
105
- };
106
- if (verifyAuth) {
107
- httpServer.on("upgrade", (req, socket) => {
108
- const rewritten = rewriteAtPathPrefix(req.url ?? "");
109
- if (rewritten !== null) req.url = rewritten;
110
- if (!verifyAuth(req)) {
111
- socket.write("HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n");
112
- socket.destroy();
113
- notifyAuthReject("ws-upgrade");
114
- return;
115
- }
116
- });
117
- httpServer.on("request", (req, res) => {
118
- const rewritten = rewriteAtPathPrefix(req.url ?? "");
119
- if (rewritten === null) return;
120
- req.url = rewritten;
121
- if (!verifyAuth(req)) {
122
- res.statusCode = 401;
123
- res.end();
124
- notifyAuthReject("http-request");
125
- }
126
- });
127
- }
128
- await loadChiiServer().start({
129
- server: httpServer,
130
- domain: `${host}:${requestedPort}`,
131
- port: requestedPort
132
- });
133
- const actualPort = await new Promise((resolve, reject) => {
134
- httpServer.once("error", reject);
135
- httpServer.listen(requestedPort, host, () => {
136
- httpServer.off("error", reject);
137
- resolve(httpServer.address().port);
138
- });
139
- });
140
- return {
141
- port: actualPort,
142
- baseUrl: `http://${host}:${actualPort}`,
143
- close: () => new Promise((resolve) => {
144
- httpServer.close(() => resolve());
145
- })
146
- };
147
- }
148
- //#endregion
149
- exports.startChiiRelay = startChiiRelay;
150
-
151
- //# sourceMappingURL=chii-relay-DngjQ2_A.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chii-relay-DngjQ2_A.cjs","names":["require"],"sources":["../src/mcp/chii-relay.ts"],"sourcesContent":["/**\n * Boots the local Chii relay server.\n *\n * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome\n * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.\n * The relay accepts a `target` websocket from the phone's injected `target.js`\n * and `client` websockets from CDP frontends (our MCP connection).\n *\n * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app\n * entries.\n *\n * TOTP auth (relay-side, authoritative gate):\n * When `verifyAuth` is provided, this module registers HTTP 'upgrade' and\n * 'request' listeners on the server BEFORE calling `chii.start({server})`.\n * Node's `http.Server` calls listeners in registration order; the first to\n * call `socket.destroy()` (upgrade) or `res.end()` (request) wins. Invalid\n * auth → 401 + destroy (chii never sees the connection). Valid auth →\n * return without side-effect (chii handles it).\n *\n * TOTP code transports (issue #466) — two equivalent ways to carry the code:\n * 1. Query param `at=<code>` — used by the daemon-side `/client` connection\n * (`chii-connection.ts` appends it; it holds the secret).\n * 2. Path prefix `/at/<code>/…` — used by the phone-side target. Chii's\n * stock `target.js` derives its WS endpoint from the script `src`\n * (`scriptEl.src.replace('target.js','')`), so the only way for the\n * phone to carry a code is to embed it in the script URL path. The\n * in-app attach injects `https://<host>/at/<code>/target.js`; both the\n * script fetch and the derived `wss://<host>/at/<code>/target/<id>` WS\n * dial then carry the prefix. The listeners below rewrite the prefix\n * into the query form (`rewriteAtPathPrefix`) and MUTATE `req.url`\n * before chii's own handlers (registered later) parse it — chii only\n * ever sees the stripped URL.\n *\n * Threat model: \"URL leak\" — someone obtains the tunnel URL (Slack paste, QR\n * screenshot, shoulder-surfing) but does not have the shared TOTP secret.\n * Rotating 6-digit code makes the URL stale after 30 s.\n * A determined attacker who extracts the secret from the dogfood bundle can\n * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).\n *\n * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear\n * in any log, error message, or process output. `verifyAuth` is a black-box\n * predicate from the caller's perspective; this module only forwards pass/fail.\n */\n\nimport { createServer, type IncomingMessage, type Server } from 'node:http';\nimport { createRequire } from 'node:module';\nimport type { AddressInfo } from 'node:net';\nimport type { Duplex } from 'node:stream';\n\nconst require = createRequire(import.meta.url);\n\n/** `chii/server` is CommonJS and shipped without TypeScript types. */\ninterface ChiiServerModule {\n start(options: {\n port?: number;\n host?: string;\n domain?: string;\n server?: Server;\n basePath?: string;\n }): Promise<void>;\n}\n\nfunction loadChiiServer(): ChiiServerModule {\n // `chii`'s package `main` is `./server/index.js`, exposing `{ start }`.\n const mod: unknown = require('chii');\n if (\n typeof mod === 'object' &&\n mod !== null &&\n 'start' in mod &&\n typeof (mod as { start: unknown }).start === 'function'\n ) {\n return mod as ChiiServerModule;\n }\n throw new Error('chii server module did not expose start()');\n}\n\nexport interface ChiiRelay {\n port: number;\n /** Base URL for the relay HTTP/WS server, e.g. `http://127.0.0.1:54321`. */\n baseUrl: string;\n close(): Promise<void>;\n}\n\n/**\n * Secret-free metadata about a single auth rejection (issue #467).\n *\n * SECRET-HANDLING: this event carries ONLY the surface kind. It must never\n * grow fields for `req.url`, query strings, codes, or secrets — observers\n * (diagnostics counters, console hints) only need \"a rejection happened\".\n */\nexport interface RelayAuthRejectEvent {\n /** Which inbound surface was rejected. */\n kind: 'ws-upgrade' | 'http-request';\n}\n\n/**\n * Rewrites a `/at/<code>/…` path-prefixed request URL into the equivalent\n * query-based form, e.g.:\n *\n * `/at/123456/target.js` → `/target.js?at=123456`\n * `/at/123456/target/x?url=u` → `/target/x?url=u&at=123456`\n * `/at/123456/` → `/?at=123456`\n *\n * Returns `null` when the URL does not carry the prefix (including an empty\n * code segment) — callers fall back to the unmodified URL and the existing\n * query-based auth path.\n *\n * Pure string surgery — this function knows nothing about secrets or code\n * validity; verification stays inside the caller-provided `verifyAuth`\n * predicate (which parses the query). The raw path segment is appended\n * verbatim to the query: both path segments and query values are\n * percent-decoded exactly once by their consumers, so no re-encoding is\n * needed (TOTP codes are 6 digits and never percent-encoded in practice).\n */\nexport function rewriteAtPathPrefix(rawUrl: string): string | null {\n const match = /^\\/at\\/([^/?]+)(\\/[^?]*)?(\\?.*)?$/.exec(rawUrl);\n if (match === null) return null;\n const code = match[1];\n const path = match[2] === undefined || match[2] === '' ? '/' : match[2];\n const query = match[3] ?? '';\n const separator = query === '' ? '?' : '&';\n return `${path}${query}${separator}at=${code}`;\n}\n\nexport interface StartChiiRelayOptions {\n /**\n * Local port for the relay. Default 0 (OS-assigned ephemeral port).\n *\n * Using 0 means the OS picks a free port — this is the safe default because\n * a stale cloudflared child process (PPID 1, orphaned after SIGKILL) may still\n * be holding a fixed port. A fixed port causes EADDRINUSE on the next startup,\n * which makes the MCP handshake fail with -32000. With port 0 the new relay\n * always gets a fresh port, making any orphaned process harmless.\n *\n * Pass an explicit number to restore fixed-port behaviour (backwards-compatible).\n */\n port?: number;\n /** Bind host. Default 127.0.0.1 (tunnel reaches it locally). */\n host?: string;\n /**\n * Optional auth predicate for WebSocket upgrade requests.\n *\n * When provided, every inbound WebSocket upgrade is checked by calling\n * `verifyAuth(req)` before Chii processes it. Return `true` to allow the\n * upgrade; return `false` to reject with HTTP 401 and destroy the socket.\n *\n * The predicate MUST NOT log the secret or any TOTP code — it is a black-box\n * from this module's perspective.\n *\n * @param req - The raw HTTP `IncomingMessage` from the upgrade handshake.\n * Inspect `req.url` for query parameters (e.g. `at=<code>`). Path-prefixed\n * URLs (`/at/<code>/…`, the phone-target transport — issue #466) are\n * rewritten into the query form BEFORE this predicate runs, so a\n * query-only predicate covers both transports.\n * @returns `true` if the upgrade is authorised, `false` to reject.\n */\n verifyAuth?: (req: IncomingMessage) => boolean;\n /**\n * Secret-free observability callback fired on every auth rejection\n * (issue #467). Only meaningful together with `verifyAuth`.\n *\n * SECRET-HANDLING: the event carries ONLY the rejection kind — never\n * `req.url`, query strings, TOTP codes, or the secret. Implementations must\n * keep it that way (e.g. increment a counter + timestamp). Exceptions thrown\n * by the callback are swallowed so observability can never break the gate.\n */\n onAuthReject?: (event: RelayAuthRejectEvent) => void;\n}\n\n/**\n * Starts the Chii relay and resolves once listening.\n *\n * Default port is 0 (OS-assigned). With port 0 the OS picks a free ephemeral\n * port on every start, so a stale cloudflared orphan holding any particular\n * port cannot cause EADDRINUSE. The resolved `ChiiRelay.port` and `baseUrl`\n * always reflect the actual bound port.\n *\n * chii.start() is called with `server` (our pre-created httpServer) BEFORE\n * httpServer.listen(). This is intentional: chii attaches its Koa handler and\n * WS upgrade listener to the server object, but the actual TCP bind is\n * performed by our httpServer.listen() call below. The `port`/`domain` values\n * passed to chii.start() are used for display/banner purposes inside chii and\n * do not affect which port the server binds. The connection path (clients\n * connecting to `relay.baseUrl`) always uses the post-listen confirmed port.\n */\nexport async function startChiiRelay(options: StartChiiRelayOptions = {}): Promise<ChiiRelay> {\n const requestedPort = options.port ?? 0;\n const host = options.host ?? '127.0.0.1';\n const { verifyAuth, onAuthReject } = options;\n\n const httpServer = createServer();\n\n // Secret-free observability hook (issue #467). Swallow callback exceptions —\n // a broken observer must never turn into an open gate or a crashed relay.\n const notifyAuthReject = (kind: RelayAuthRejectEvent['kind']): void => {\n if (onAuthReject === undefined) return;\n try {\n onAuthReject({ kind });\n } catch {\n // Ignore — observability is best-effort.\n }\n };\n\n // Register our auth listeners BEFORE chii.start() so they fire first.\n // Node's http.Server emits 'upgrade'/'request' to all listeners in\n // registration order; the first to destroy() the socket (upgrade) or end()\n // the response (request) wins. Valid requests return without side-effect so\n // chii's own handlers take over normally — and because listeners run\n // synchronously in order, mutating `req.url` here (path-prefix strip,\n // issue #466) means chii's later-registered handlers only ever see the\n // stripped URL.\n //\n // We only register when verifyAuth is provided so the no-auth path is\n // zero-overhead for tests and local-only dev sessions. (The phone-side\n // `/at/<code>/` prefix only ever appears when TOTP is armed — the launcher\n // QR carries the `at` code — so the no-auth path never needs the strip.)\n if (verifyAuth) {\n httpServer.on('upgrade', (req: IncomingMessage, socket: Duplex) => {\n // Phone-target transport (issue #466): normalise a `/at/<code>/…` path\n // prefix into the query form before verification, and strip it from the\n // URL chii will see. No-prefix URLs pass through untouched (daemon\n // client query transport — back-compat).\n const rewritten = rewriteAtPathPrefix(req.url ?? '');\n if (rewritten !== null) {\n req.url = rewritten;\n }\n if (!verifyAuth(req)) {\n // Reject: send a minimal HTTP 401 response and close the socket.\n // We do NOT log req.url or any auth param here to avoid leaking codes.\n socket.write('HTTP/1.1 401 Unauthorized\\r\\nContent-Length: 0\\r\\n\\r\\n');\n socket.destroy();\n notifyAuthReject('ws-upgrade');\n // Early return — chii's handler is NOT called for this socket.\n return;\n }\n // Auth passed: no-op. Chii's upgrade listener (registered below by\n // chii.start) will handle the rest.\n });\n\n // Plain HTTP requests: only the path-prefixed form is ours — the phone\n // fetches `target.js` via `https://<host>/at/<code>/target.js` (issue\n // #466), which must be verified + stripped so chii's Koa static handler\n // serves `/target.js`. Non-prefixed requests keep today's behaviour\n // (ungated pass-through to chii).\n httpServer.on('request', (req, res) => {\n const rewritten = rewriteAtPathPrefix(req.url ?? '');\n if (rewritten === null) return;\n req.url = rewritten;\n if (!verifyAuth(req)) {\n // We do NOT log req.url or any auth param here to avoid leaking codes.\n res.statusCode = 401;\n res.end();\n notifyAuthReject('http-request');\n }\n // Auth passed: no-op — chii's Koa 'request' listener (registered below\n // by chii.start) serves the rewritten URL. (Koa skips writing when an\n // earlier listener already ended the response, so the 401 path is safe\n // even though Koa still runs.)\n });\n }\n\n const chii = loadChiiServer();\n // Passing an existing `server` makes chii attach its Koa handler + WS upgrade\n // to our HTTP server rather than creating its own listener.\n // Note: port/domain here are display-only inside chii — the TCP bind is ours.\n await chii.start({ server: httpServer, domain: `${host}:${requestedPort}`, port: requestedPort });\n\n const actualPort = await new Promise<number>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(requestedPort, host, () => {\n httpServer.off('error', reject);\n // httpServer.address() is non-null immediately after the listen callback.\n const addr = httpServer.address() as AddressInfo;\n resolve(addr.port);\n });\n });\n\n return {\n port: actualPort,\n baseUrl: `http://${host}:${actualPort}`,\n close: () =>\n new Promise<void>((resolve) => {\n httpServer.close(() => resolve());\n }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAMA,aAAAA,0BAAAA,eAAAA,QAAAA,MAAAA,CAAAA,cAAAA,WAAAA,CAAAA,KAAwC;AAa9C,SAAS,iBAAmC;CAE1C,MAAM,MAAeA,UAAQ,OAAO;AACpC,KACE,OAAO,QAAQ,YACf,QAAQ,QACR,WAAW,OACX,OAAQ,IAA2B,UAAU,WAE7C,QAAO;AAET,OAAM,IAAI,MAAM,4CAA4C;;;;;;;;;;;;;;;;;;;;;AAyC9D,SAAgB,oBAAoB,QAA+B;CACjE,MAAM,QAAQ,oCAAoC,KAAK,OAAO;AAC9D,KAAI,UAAU,KAAM,QAAO;CAC3B,MAAM,OAAO,MAAM;CACnB,MAAM,OAAO,MAAM,OAAO,KAAA,KAAa,MAAM,OAAO,KAAK,MAAM,MAAM;CACrE,MAAM,QAAQ,MAAM,MAAM;AAE1B,QAAO,GAAG,OAAO,QADC,UAAU,KAAK,MAAM,IACJ,KAAK;;;;;;;;;;;;;;;;;;AAgE1C,eAAsB,eAAe,UAAiC,EAAE,EAAsB;CAC5F,MAAM,gBAAgB,QAAQ,QAAQ;CACtC,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,EAAE,YAAY,iBAAiB;CAErC,MAAM,cAAA,GAAA,UAAA,eAA2B;CAIjC,MAAM,oBAAoB,SAA6C;AACrE,MAAI,iBAAiB,KAAA,EAAW;AAChC,MAAI;AACF,gBAAa,EAAE,MAAM,CAAC;UAChB;;AAkBV,KAAI,YAAY;AACd,aAAW,GAAG,YAAY,KAAsB,WAAmB;GAKjE,MAAM,YAAY,oBAAoB,IAAI,OAAO,GAAG;AACpD,OAAI,cAAc,KAChB,KAAI,MAAM;AAEZ,OAAI,CAAC,WAAW,IAAI,EAAE;AAGpB,WAAO,MAAM,yDAAyD;AACtE,WAAO,SAAS;AAChB,qBAAiB,aAAa;AAE9B;;IAIF;AAOF,aAAW,GAAG,YAAY,KAAK,QAAQ;GACrC,MAAM,YAAY,oBAAoB,IAAI,OAAO,GAAG;AACpD,OAAI,cAAc,KAAM;AACxB,OAAI,MAAM;AACV,OAAI,CAAC,WAAW,IAAI,EAAE;AAEpB,QAAI,aAAa;AACjB,QAAI,KAAK;AACT,qBAAiB,eAAe;;IAMlC;;AAOJ,OAJa,gBAAgB,CAIlB,MAAM;EAAE,QAAQ;EAAY,QAAQ,GAAG,KAAK,GAAG;EAAiB,MAAM;EAAe,CAAC;CAEjG,MAAM,aAAa,MAAM,IAAI,SAAiB,SAAS,WAAW;AAChE,aAAW,KAAK,SAAS,OAAO;AAChC,aAAW,OAAO,eAAe,YAAY;AAC3C,cAAW,IAAI,SAAS,OAAO;AAG/B,WADa,WAAW,SAAS,CACpB,KAAK;IAClB;GACF;AAEF,QAAO;EACL,MAAM;EACN,SAAS,UAAU,KAAK,GAAG;EAC3B,aACE,IAAI,SAAe,YAAY;AAC7B,cAAW,YAAY,SAAS,CAAC;IACjC;EACL"}