diff --git a/package-lock.json b/package-lock.json index 43e3ba1a..bc33c4bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,8 +58,6 @@ }, "node_modules/@clack/core": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.1.tgz", - "integrity": "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==", "license": "MIT", "dependencies": { "fast-wrap-ansi": "^0.2.0", @@ -71,8 +69,6 @@ }, "node_modules/@clack/prompts": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.4.0.tgz", - "integrity": "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==", "license": "MIT", "dependencies": { "@clack/core": "1.3.1", @@ -95,435 +91,78 @@ "node": ">=12" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@fails-components/webtransport": { + "version": "1.6.3", + "license": "BSD-3-Clause", + "dependencies": { + "@types/debug": "^4.1.7", + "bindings": "^1.5.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=18" + "node": ">=20" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@fails-components/webtransport-transport-http3-quiche": { + "version": "1.6.3", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/debug": "^4.1.7", + "bindings": "^1.5.0", + "cmake-js": "^8.0.0", + "debug": "^4.3.4", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1" + }, "engines": { - "node": ">=18" + "node": ">=20" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@fails-components/webtransport-transport-http3-quiche/node_modules/debug": { + "version": "4.4.3", "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=18" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } + "node_modules/@fails-components/webtransport-transport-http3-quiche/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "node_modules/@fails-components/webtransport-transport-http3-quiche/node_modules/node-addon-api": { + "version": "7.1.1", + "license": "MIT" }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@fails-components/webtransport/node_modules/debug": { + "version": "4.4.3", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=18" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "node_modules/@fails-components/webtransport/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" }, "node_modules/@hono/node-server": { "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -532,6 +171,16 @@ "hono": "^4" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "dev": true, @@ -556,8 +205,6 @@ }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -856,306 +503,134 @@ "node": ">= 0.6" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@moq/lite": { + "version": "0.2.4", + "license": "(MIT OR Apache-2.0)", "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "@moq/qmux": "^0.0.6", + "@moq/signals": "^0.1.6", + "async-mutex": "^0.5.0" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "zod": "^4.0.0" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "dev": true, - "license": "MIT", + "node_modules/@moq/net": { + "version": "0.1.1", + "license": "(MIT OR Apache-2.0)", "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "@moq/qmux": "^0.0.6", + "@moq/signals": "^0.1.6", + "async-mutex": "^0.5.0" + }, + "peerDependencies": { + "zod": "^4.0.0" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "node_modules/@moq/qmux": { + "version": "0.0.6", + "license": "(MIT OR Apache-2.0)" + }, + "node_modules/@moq/signals": { + "version": "0.1.6", + "license": "(MIT OR Apache-2.0)", + "peerDependencies": { + "@types/react": "^19.1.8", + "react": "^19.0.0", + "solid-js": "^1.9.7" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "solid-js": { + "optional": true + } } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", - "cpu": [ - "x64" - ], + "node_modules/@noble/hashes": { + "version": "1.8.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", - "cpu": [ - "arm64" - ], + "node_modules/@oxc-project/types": { + "version": "0.130.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": "^20.19.0 || >=22.12.0" @@ -1163,15 +638,11 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, @@ -1199,17 +670,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "dev": true, @@ -1221,8 +681,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1243,17 +701,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.13", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1294,9 +755,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.19.11", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1384,8 +848,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", - "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", "dev": true, "license": "MIT", "dependencies": { @@ -1402,8 +864,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", - "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1429,8 +889,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", - "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", "dev": true, "license": "MIT", "dependencies": { @@ -1442,8 +900,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", - "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1456,8 +912,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", - "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", "dev": true, "license": "MIT", "dependencies": { @@ -1472,8 +926,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", - "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", "dev": true, "license": "MIT", "funding": { @@ -1482,8 +934,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", - "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1557,6 +1007,26 @@ } } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "dev": true, @@ -1573,19 +1043,58 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { "node": ">=12" } }, + "node_modules/async-mutex": { + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.4", "license": "MIT", @@ -1608,6 +1117,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -1642,14 +1173,105 @@ }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/chownr": { + "version": "3.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cmake-js": { + "version": "8.0.0", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "fs-extra": "^11.3.3", + "node-api-headers": "^1.8.0", + "rc": "1.2.8", + "semver": "^7.7.3", + "tar": "^7.5.6", + "url-join": "^4.0.1", + "which": "^6.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "cmake-js": "bin/cmake-js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cmake-js/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/cmake-js/node_modules/isexe": { + "version": "4.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/cmake-js/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/cmake-js/node_modules/which": { + "version": "6.0.1", + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "dev": true, @@ -1688,8 +1310,6 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, @@ -1741,11 +1361,31 @@ "node": ">= 8" } }, - "node_modules/debug": { - "version": "2.6.9", + "node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=4.0.0" } }, "node_modules/delayed-stream": { @@ -1773,9 +1413,6 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1814,6 +1451,10 @@ "version": "1.1.1", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "license": "MIT", @@ -1821,6 +1462,13 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "license": "MIT", @@ -1859,14 +1507,19 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1897,6 +1550,13 @@ "node": ">=18.0.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect-type": { "version": "1.3.0", "dev": true, @@ -1951,8 +1611,6 @@ }, "node_modules/express-rate-limit": { "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", "dependencies": { "ip-address": "^10.2.0" @@ -1978,14 +1636,10 @@ }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", "license": "MIT" }, "node_modules/fast-string-width": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", "license": "MIT", "dependencies": { "fast-string-truncated-width": "^3.0.2" @@ -1993,8 +1647,6 @@ }, "node_modules/fast-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -2009,8 +1661,6 @@ }, "node_modules/fast-wrap-ansi": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", - "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", "license": "MIT", "dependencies": { "fast-string-width": "^3.0.2" @@ -2018,8 +1668,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -2034,6 +1682,10 @@ } } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.2", "license": "MIT", @@ -2095,12 +1747,25 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.5", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -2117,6 +1782,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "license": "MIT", @@ -2150,6 +1822,10 @@ "node": ">= 0.4" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "license": "MIT", @@ -2160,6 +1836,10 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, "node_modules/has-symbols": { "version": "1.1.0", "license": "MIT", @@ -2196,8 +1876,6 @@ }, "node_modules/hono": { "version": "4.12.19", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.19.tgz", - "integrity": "sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -2231,14 +1909,34 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/inherits": { "version": "2.0.4", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "license": "ISC" + }, "node_modules/ip-address": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -2251,6 +1949,13 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "4.0.0", "license": "MIT" @@ -2276,262 +1981,56 @@ }, "node_modules/jsonc-parser": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "license": "MIT" }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", + "node_modules/jsonfile": { + "version": "6.2.1", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "universalify": "^2.0.0" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "graceful-fs": "^4.1.6" } }, - "node_modules/lightningcss-linux-x64-musl": { + "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], "dev": true, "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "detect-libc": "^2.0.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/lightningcss-win32-x64-msvc": { + "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": ">= 12.0.0" @@ -2541,6 +2040,10 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/long": { + "version": "5.3.2", + "license": "Apache-2.0" + }, "node_modules/magic-string": { "version": "0.30.21", "dev": true, @@ -2609,14 +2112,50 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT" + }, "node_modules/ms": { "version": "2.0.0", "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -2632,6 +2171,10 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "license": "MIT", @@ -2639,6 +2182,16 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.92.0", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "8.6.0", "license": "MIT", @@ -2646,6 +2199,10 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/node-api-headers": { + "version": "1.9.0", + "license": "MIT" + }, "node_modules/node-gyp-build": { "version": "4.8.4", "license": "MIT", @@ -2714,14 +2271,10 @@ }, "node_modules/path-to-regexp": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, @@ -2731,8 +2284,6 @@ }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2760,8 +2311,6 @@ }, "node_modules/postcss": { "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -2787,10 +2336,32 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prettier": { "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -2803,6 +2374,28 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/protobufjs": { + "version": "7.6.1", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -2814,6 +2407,14 @@ "node": ">= 0.10" } }, + "node_modules/pump": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qs": { "version": "6.14.2", "license": "BSD-3-Clause", @@ -2847,6 +2448,38 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "license": "MIT", @@ -2856,8 +2489,6 @@ }, "node_modules/rolldown": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2923,8 +2554,6 @@ }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2955,8 +2584,6 @@ }, "node_modules/semver": { "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3094,10 +2721,49 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/smol-toml": { @@ -3129,6 +2795,42 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/superagent": { "version": "10.3.0", "dev": true, @@ -3201,6 +2903,48 @@ "node": ">=6.6.0" } }, + "node_modules/tar": { + "version": "7.5.15", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tinybench": { "version": "2.9.0", "dev": true, @@ -3216,8 +2960,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { @@ -3233,8 +2975,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -3336,11 +3076,17 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } }, "node_modules/type-is": { "version": "1.6.18", @@ -3367,9 +3113,15 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "license": "MIT", @@ -3377,6 +3129,14 @@ "node": ">= 0.8" } }, + "node_modules/url-join": { + "version": "4.0.1", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "license": "MIT", @@ -3398,8 +3158,6 @@ }, "node_modules/vite": { "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "dev": true, "license": "MIT", "dependencies": { @@ -3476,8 +3234,6 @@ }, "node_modules/vitest": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", - "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3602,6 +3358,21 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "license": "ISC" @@ -3625,10 +3396,22 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -3640,6 +3423,29 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yargs": { + "version": "17.7.2", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "dev": true, @@ -3650,8 +3456,6 @@ }, "node_modules/zod": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -3670,6 +3474,8 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { + "@fails-components/webtransport": "^1.6.3", + "@fails-components/webtransport-transport-http3-quiche": "^1.6.3", "@modelcontextprotocol/sdk": "^1.20.0" }, "bin": { @@ -3707,8 +3513,6 @@ }, "packages/argent-cli/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3717,8 +3521,6 @@ }, "packages/argent-cli/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3731,8 +3533,6 @@ }, "packages/argent-cli/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -3758,8 +3558,6 @@ }, "packages/argent-installer/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3768,8 +3566,6 @@ }, "packages/argent-installer/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3782,8 +3578,6 @@ }, "packages/argent-installer/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -3802,8 +3596,6 @@ }, "packages/argent-mcp/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3812,8 +3604,6 @@ }, "packages/argent-mcp/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3826,8 +3616,6 @@ }, "packages/argent-mcp/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -3842,8 +3630,6 @@ }, "packages/argent-tools-client/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3852,8 +3638,6 @@ }, "packages/argent-tools-client/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3866,15 +3650,11 @@ }, "packages/argent-tools-client/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, "packages/argent/node_modules/@esbuild/darwin-arm64": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -3890,8 +3670,6 @@ }, "packages/argent/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3900,8 +3678,6 @@ }, "packages/argent/node_modules/esbuild": { "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3942,8 +3718,6 @@ }, "packages/argent/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3956,8 +3730,6 @@ }, "packages/argent/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -3979,8 +3751,6 @@ }, "packages/native-devtools-ios/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3989,8 +3759,6 @@ }, "packages/native-devtools-ios/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4003,8 +3771,6 @@ }, "packages/native-devtools-ios/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -4021,8 +3787,6 @@ }, "packages/registry/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4049,8 +3813,13 @@ "@argent/registry": "file:../registry", "@argent/update-core": "file:../update-core", "@clack/prompts": "^1.1.0", + "@fails-components/webtransport": "^1.6.3", + "@fails-components/webtransport-transport-http3-quiche": "^1.6.3", + "@moq/lite": "^0.2.4", + "@moq/net": "^0.1.1", "express": "^4.19.2", "pngjs": "^7.0.0", + "protobufjs": "^7.6.1", "semver": "^7.7.4", "source-map-js": "^1.2.1", "tree-sitter": "^0.21.1", @@ -4074,8 +3843,6 @@ }, "packages/tool-server/node_modules/@types/node": { "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4084,8 +3851,6 @@ }, "packages/tool-server/node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4098,8 +3863,6 @@ }, "packages/tool-server/node_modules/undici-types": { "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, diff --git a/packages/argent-mcp/src/content.ts b/packages/argent-mcp/src/content.ts index 7172c02d..e5fc2d15 100644 --- a/packages/argent-mcp/src/content.ts +++ b/packages/argent-mcp/src/content.ts @@ -16,11 +16,22 @@ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0 // Without this check, a 404 (file missing), an HTML error page, or any other // non-PNG response would be base64'd and labelled `image/png`, which the // model API rejects with "Image could not be processed" (issue #255). +// +// `file://` URLs are handled directly via the fs module — Node's built-in +// `fetch` only supports `http(s)://`, and the ios-remote screenshot path +// writes PNGs to a temp dir and returns a `file://` URL. async function fetchPngBytes(url: string): Promise { try { - const res = await fetch(url); - if (!res.ok) return null; - const buf = Buffer.from(await res.arrayBuffer()); + let buf: Buffer; + if (url.startsWith("file://")) { + const { readFile } = await import("node:fs/promises"); + const { fileURLToPath } = await import("node:url"); + buf = await readFile(fileURLToPath(url)); + } else { + const res = await fetch(url); + if (!res.ok) return null; + buf = Buffer.from(await res.arrayBuffer()); + } if (buf.length < PNG_SIGNATURE.length) return null; if (!buf.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return null; return buf; diff --git a/packages/argent/package.json b/packages/argent/package.json index c26f7d4a..645b2061 100644 --- a/packages/argent/package.json +++ b/packages/argent/package.json @@ -41,6 +41,8 @@ "scripts/postinstall.cjs" ], "dependencies": { + "@fails-components/webtransport": "^1.6.3", + "@fails-components/webtransport-transport-http3-quiche": "^1.6.3", "@modelcontextprotocol/sdk": "^1.20.0" }, "devDependencies": { diff --git a/packages/argent/scripts/bundle-tools.cjs b/packages/argent/scripts/bundle-tools.cjs index 65d205aa..ab8efe3c 100644 --- a/packages/argent/scripts/bundle-tools.cjs +++ b/packages/argent/scripts/bundle-tools.cjs @@ -95,6 +95,17 @@ if (fs.existsSync(ANDROID_APK_DEST_DIR)) { fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true }); // Bundle the tools server +// +// `@fails-components/webtransport` and its http3-quiche transport ship native +// addons (quiche, prebuilt .node binaries) that can't be inlined into a single +// CJS bundle. Keep them external so npm resolves them from the published +// package's `node_modules/` at runtime — they're listed in +// `@swmansion/argent`'s `dependencies` so they install alongside the package. +const TOOL_SERVER_EXTERNAL = [ + "@fails-components/webtransport", + "@fails-components/webtransport-transport-http3-quiche", +]; + esbuild.buildSync({ entryPoints: [TOOLS_ENTRY], bundle: true, @@ -104,6 +115,7 @@ esbuild.buildSync({ outfile: OUT_FILE, alias: ALIASES, mainFields: MAIN_FIELDS, + external: TOOL_SERVER_EXTERNAL, }); console.log(`✓ Bundled tools server → ${path.relative(process.cwd(), OUT_FILE)}`); diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 87c5c109..92351f3a 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -62,7 +62,7 @@ export interface InvokeToolOptions { // ── Device + Capability Types ── -export type Platform = "ios" | "android"; +export type Platform = "ios" | "android" | "ios-remote"; export type DeviceKind = "simulator" | "emulator" | "device" | "unknown"; @@ -90,6 +90,15 @@ export interface ToolCapability { simulator?: boolean; device?: boolean; }; + /** + * Remote-iOS support, driven via `sim-remote`. Independent matrix from + * `apple` because remote sims have different host-binary requirements + * (`sim-remote` instead of `xcrun`) and a different transport stack + * (MoQ + TCP proxy instead of local WebSocket + Unix sockets). + */ + appleRemote?: { + simulator?: boolean; + }; android?: { emulator?: boolean; device?: boolean; @@ -119,7 +128,7 @@ export interface ToolCapability { * On a missing binary, the HTTP layer returns 424 Failed Dependency with an * install hint the agent can surface verbatim. */ -export type ToolDependency = "adb" | "xcrun" | "emulator"; +export type ToolDependency = "adb" | "xcrun" | "emulator" | "sim-remote"; // ── Tool Types ── diff --git a/packages/tool-server/package.json b/packages/tool-server/package.json index 1f61c8bb..8ed45984 100644 --- a/packages/tool-server/package.json +++ b/packages/tool-server/package.json @@ -13,13 +13,18 @@ "typecheck:tests": "tsc --noEmit -p tsconfig.test.json" }, "dependencies": { - "@argent/native-devtools-ios": "file:../native-devtools-ios", "@argent/native-devtools-android": "file:../native-devtools-android", + "@argent/native-devtools-ios": "file:../native-devtools-ios", "@argent/registry": "file:../registry", "@argent/update-core": "file:../update-core", "@clack/prompts": "^1.1.0", + "@fails-components/webtransport": "^1.6.3", + "@fails-components/webtransport-transport-http3-quiche": "^1.6.3", + "@moq/lite": "^0.2.4", + "@moq/net": "^0.1.1", "express": "^4.19.2", "pngjs": "^7.0.0", + "protobufjs": "^7.6.1", "semver": "^7.7.4", "source-map-js": "^1.2.1", "tree-sitter": "^0.21.1", diff --git a/packages/tool-server/src/blueprints/ax-service.ts b/packages/tool-server/src/blueprints/ax-service.ts index 3ea0c01f..dd95aa99 100644 --- a/packages/tool-server/src/blueprints/ax-service.ts +++ b/packages/tool-server/src/blueprints/ax-service.ts @@ -1,11 +1,7 @@ import * as net from "node:net"; import * as fs from "node:fs"; -import * as fsAsync from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; import * as readline from "node:readline"; -import { promisify } from "node:util"; -import { execFile, ChildProcess } from "node:child_process"; +import { ChildProcess } from "node:child_process"; import { TypedEventEmitter, type DeviceInfo, @@ -13,17 +9,20 @@ import { type ServiceInstance, type ServiceEvents, } from "@argent/registry"; -import { axServiceBinaryPath, axServiceBinaryPathTcp } from "@argent/native-devtools-ios"; -import { SIMCTL_SPAWN_TIMEOUT_MS } from "../utils/simctl-config"; +import { pickIosHost, type IosEndpoint } from "../utils/ios-host"; -const execFileAsync = promisify(execFile); +// Re-export AX-pref helpers that used to live here so existing callers +// (boot-device, simulator-server) keep their import paths. +export { + ensureAutomationEnabled, + isEntitlementBypassActive, + setAccessibilityPrefsPreBoot, +} from "../utils/ax-prefs"; export const AX_SERVICE_NAMESPACE = "AXService"; export type AXServiceTransport = "unix" | "tcp"; -export const AX_SERVICE_TCP_PORT = Number(process.env.AX_SERVICE_TCP_PORT) || 9231; - // Same DeviceInfo-via-options pattern as the other iOS-only blueprints. type AxServiceFactoryOptions = Record & { device: DeviceInfo; @@ -75,115 +74,18 @@ function getSocketPath(udid: string): string { return `/tmp/ax-${udid.slice(0, 8)}.sock`; } -type AXEndpoint = { transport: "unix"; socketPath: string } | { transport: "tcp"; port: number }; - interface PendingRpc { resolve: (value: unknown) => void; reject: (err: Error) => void; timer: ReturnType; } -export async function ensureAutomationEnabled(udid: string): Promise { - await execFileAsync( - "xcrun", - [ - "simctl", - "spawn", - udid, - "defaults", - "write", - "com.apple.Accessibility", - "AutomationEnabled", - "-bool", - "true", - ], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ); -} - -/** - * Check whether `IgnoreAXServerEntitlements` is active on this sim. - * - * iOS 26.5+: SB's AX server rejects unentitled MIG clients with - * kAXError -25215. The pref disables the check, but SB caches it at - * init — writing it post-boot has no effect until the next restart. - * The only effective path is the pre-boot plist write in boot-device. - * - * This read-only probe tells the caller whether the pre-boot write - * happened so describe can surface a degraded-quality hint when it didn't. - */ -export async function isEntitlementBypassActive(udid: string): Promise { - return execFileAsync( - "xcrun", - [ - "simctl", - "spawn", - udid, - "defaults", - "read", - "com.apple.Accessibility", - "IgnoreAXServerEntitlements", - ], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ) - .then(({ stdout }) => stdout.trim() === "1") - .catch(() => false); -} - -/** - * Host-side `com.apple.Accessibility` plist inside the sim's data container. - * Writeable while Shutdown; in-sim cfprefsd overwrites it once Booted. - */ -function accessibilityPlistPath(udid: string): string { - return path.join( - os.homedir(), - "Library/Developer/CoreSimulator/Devices", - udid, - "data/Library/Preferences/com.apple.Accessibility.plist" - ); -} - -/** - * Write the four AX prefs to the sim's host plist BEFORE `simctl boot` so SB - * caches them at AX-server init and never needs the disruptive kickstart - * (which kills the foreground app and dismisses in-flight system alerts). - * - * All four are required on a freshly-erased sim: - * - `IgnoreAXServerEntitlements` bypasses the iOS 26.5+ kAXErrorNotEntitled check. - * - `AutomationEnabled` opts the simctl-spawned ax-service in as an AX client. - * - `AccessibilityEnabled` + `ApplicationAccessibilityEnabled` gate the AT - * subsystem bootstrap. Without them SB never spawns `AccessibilityUIServer` - * and describe returns an empty ROOT even though the entitlement check passes - * (reproduced on a wiped iPhone 17e: AccessibilityUIServer active count = 0 - * without these two; auto-spawns at boot with them). - * - * Caller must ensure the sim is Shutdown — in-sim cfprefsd would otherwise - * overwrite this file on flush. - */ -export async function setAccessibilityPrefsPreBoot(udid: string): Promise { - const plistPath = accessibilityPlistPath(udid); - await fsAsync.mkdir(path.dirname(plistPath), { recursive: true }); - const exists = await fsAsync - .access(plistPath) - .then(() => true) - .catch(() => false); - if (!exists) { - await execFileAsync("plutil", ["-create", "binary1", plistPath]); - } - for (const key of [ - "AutomationEnabled", - "IgnoreAXServerEntitlements", - "AccessibilityEnabled", - "ApplicationAccessibilityEnabled", - ]) { - await execFileAsync("plutil", ["-replace", key, "-bool", "true", plistPath]); - } -} - // Listen on the chosen transport. Unix: pre-unlink stale socket from previous -// runs so listen() doesn't EADDRINUSE. +// runs so listen() doesn't EADDRINUSE. TCP: when `endpoint.port` is undefined, +// bind on an OS-assigned ephemeral port and write the realized port back into +// `endpoint.port` so each per-device instance gets its own non-colliding port. function startListener( - endpoint: AXEndpoint, + endpoint: IosEndpoint, onConnection: (socket: net.Socket) => void ): Promise { return new Promise((resolve, reject) => { @@ -198,44 +100,26 @@ function startListener( const onListening = () => { server.off("error", reject); + if (endpoint.transport === "tcp") { + const addr = server.address(); + if (addr === null || typeof addr === "string") { + server.close(); + reject(new Error("ax-service server failed to bind a TCP port")); + return; + } + endpoint.port = addr.port; + } resolve(server); }; if (endpoint.transport === "tcp") { - server.listen(endpoint.port, "127.0.0.1", onListening); + server.listen(endpoint.port ?? 0, "127.0.0.1", onListening); } else { server.listen(endpoint.socketPath, onListening); } }); } -function spawnDaemon(udid: string, endpoint: AXEndpoint): ChildProcess { - const binaryPath = - endpoint.transport === "tcp" ? axServiceBinaryPathTcp() : axServiceBinaryPath(); - - const endpointArgs = - endpoint.transport === "tcp" - ? ["--port", String(endpoint.port)] - : ["--socket", endpoint.socketPath]; - - const proc = execFile( - "xcrun", - ["simctl", "spawn", udid, binaryPath, ...endpointArgs, "--timeout", "3600"], - { encoding: "utf8" } - ) as ChildProcess; - - // Defense-in-depth: a missing udid here would crash the process — - // throwing inside an async listener bypasses promise rejection and - // bubbles up as `uncaughtException`, which the tool-server treats as - // fatal. Tag with "?" instead of dereferencing. - const udidTag = typeof udid === "string" && udid.length > 0 ? udid.slice(0, 8) : "?"; - proc.stderr?.on("data", (data: string) => { - process.stderr.write(`[ax-service ${udidTag}] ${data}`); - }); - - return proc; -} - // Wait for either the daemon's TCP/UDS connection or an early exit. // Resolves with the connected socket; rejects on timeout or daemon failure. function waitForDaemonConnection( @@ -304,7 +188,7 @@ export const axServiceBlueprint: ServiceBlueprint = { } const { device } = opts; - if (device.platform !== "ios") { + if (device.platform !== "ios" && device.platform !== "ios-remote") { throw new Error( `${AX_SERVICE_NAMESPACE} is iOS-only. The target '${device.id}' classifies as Android — describe falls back to uiautomator on Android, which does not need this service.` ); @@ -320,10 +204,13 @@ export const axServiceBlueprint: ServiceBlueprint = { } const udid = device.id; - const transport: AXServiceTransport = opts.transport ?? "unix"; - const endpoint: AXEndpoint = + const host = pickIosHost(device); + // Force TCP on remote — unix sockets do not bridge across the QUIC tunnel + // sim-remote sets up between the orchestrator and the dev's machine. + const transport: AXServiceTransport = host.requiresTcp ? "tcp" : (opts.transport ?? "unix"); + const endpoint: IosEndpoint = transport === "tcp" - ? { transport: "tcp", port: AX_SERVICE_TCP_PORT } + ? { transport: "tcp" } : { transport: "unix", socketPath: getSocketPath(udid) }; const events = new TypedEventEmitter(); @@ -340,8 +227,7 @@ export const axServiceBlueprint: ServiceBlueprint = { pendingRpc.clear(); }; - await ensureAutomationEnabled(udid); - const entitlementBypassActive = await isEntitlementBypassActive(udid); + const { entitlementBypassActive } = await host.bootstrapAx(udid); // Host listens first, then we spawn the daemon and wait for it to dial in. const server = await startListener(endpoint, (socket) => { @@ -390,7 +276,15 @@ export const axServiceBlueprint: ServiceBlueprint = { }); }); - const proc = spawnDaemon(udid, endpoint); + if (endpoint.transport === "tcp") { + // Wire the reverse tunnel BEFORE asking the orchestrator to start the + // daemon — the daemon will dial 127.0.0.1: inside the simulator + // and that dial gets QUIC-forwarded back to our host listener above. + // No-op on local. `port` was populated by startListener. + await host.startProxy(udid, endpoint.port!); + } + + const proc = host.spawnAxDaemon(udid, endpoint); proc.on("exit", (code) => { if (disposed) return; @@ -415,6 +309,9 @@ export const axServiceBlueprint: ServiceBlueprint = { fs.unlinkSync(endpoint.socketPath); } catch {} } + if (endpoint.transport === "tcp") { + await host.stopProxy(udid, endpoint.port!); + } throw err; } @@ -480,6 +377,9 @@ export const axServiceBlueprint: ServiceBlueprint = { fs.unlinkSync(endpoint.socketPath); } catch {} } + if (endpoint.transport === "tcp") { + await host.stopProxy(udid, endpoint.port!); + } }, events, }; diff --git a/packages/tool-server/src/blueprints/native-devtools.ts b/packages/tool-server/src/blueprints/native-devtools.ts index 1b009111..951efdbd 100644 --- a/packages/tool-server/src/blueprints/native-devtools.ts +++ b/packages/tool-server/src/blueprints/native-devtools.ts @@ -1,23 +1,18 @@ import * as net from "node:net"; import * as fs from "node:fs"; -import * as path from "node:path"; import * as readline from "node:readline"; -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; import { TypedEventEmitter, type DeviceInfo, type ServiceBlueprint, type ServiceEvents, } from "@argent/registry"; -import { bootstrapDylibPath, bootstrapDylibPathTcp } from "@argent/native-devtools-ios"; -import { SIMCTL_SPAWN_TIMEOUT_MS } from "../utils/simctl-config"; +import { pickIosHost, buildDyldInsertLibraries, type IosEndpoint } from "../utils/ios-host"; -export type NativeDevtoolsTransport = "unix" | "tcp"; - -export const NATIVE_DEVTOOLS_TCP_PORT = Number(process.env.NATIVE_DEVTOOLS_TCP_PORT) || 9230; +// Re-exported for the env-merging unit test that imports it from this module. +export { buildDyldInsertLibraries }; -const execFileAsync = promisify(execFile); +export type NativeDevtoolsTransport = "unix" | "tcp"; export const NATIVE_DEVTOOLS_NAMESPACE = "NativeDevtools"; @@ -181,160 +176,12 @@ interface AppConnection { networkLog: NetworkEvent[]; } -/** Current bootstrap filename; `libInjectionBootstrap.dylib` is legacy (pre-rename) and still stripped when merging env. */ -const ARGENT_BOOTSTRAP_DYLIB_BASENAMES = new Set([ - "libArgentInjectionBootstrap.dylib", - "libInjectionBootstrap.dylib", -]); - function getNativeDevtoolsSocketPath(udid: string): string { // Deterministic, short — well under the 104-char macOS Unix socket limit // /tmp/argent-nd-XXXXXXXX.sock = 28 chars return `/tmp/argent-nd-${udid.slice(0, 8)}.sock`; } -function splitDyldInsertLibraries(value: string): string[] { - return value - .split(":") - .map((entry) => entry.trim()) - .filter((entry) => entry.length > 0); -} - -/** - * Strips Argent bootstrap dylibs (by basename, including the legacy pre-rename name) - * and entries that don't exist on disk (truncated artifacts from the simctl getenv - * 127-byte bug, stale paths from old installs, etc.). - * Entries starting with '@' (loader-path references) are always preserved. - * Third-party dylibs present on disk (e.g. SimCam) are kept verbatim. - */ -function shouldPreserveDyldInsertLibrariesEntry(entry: string, bootstrapPath: string): boolean { - if (entry === bootstrapPath) { - return false; - } - - if (ARGENT_BOOTSTRAP_DYLIB_BASENAMES.has(path.basename(entry))) { - return false; - } - - if (entry.startsWith("@")) { - return true; - } - - return fs.existsSync(entry); -} - -export function buildDyldInsertLibraries(currentValue: string, bootstrapPath: string): string { - const preserved = splitDyldInsertLibraries(currentValue).filter((entry) => - shouldPreserveDyldInsertLibrariesEntry(entry, bootstrapPath) - ); - return [...preserved, bootstrapPath].join(":"); -} - -async function ensureAccessibilityEnabled(udid: string): Promise { - // iOS 26+ requires AccessibilityEnabled and ApplicationAccessibilityEnabled to be set - // in the simulator's defaults for SwiftUI to populate the accessibility tree. - // Without these flags, all UIAccessibility APIs return nil/0 for SwiftUI views. - const flags = ["AccessibilityEnabled", "ApplicationAccessibilityEnabled"]; - await Promise.all( - flags.map((flag) => - execFileAsync( - "xcrun", - [ - "simctl", - "spawn", - udid, - "defaults", - "write", - "com.apple.Accessibility", - flag, - "-bool", - "true", - ], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ) - ) - ); -} - -async function ensureEnv( - udid: string, - endpoint: { transport: "unix"; socketPath: string } | { transport: "tcp"; port: number } -): Promise { - const bootstrapPath = - endpoint.transport === "tcp" ? bootstrapDylibPathTcp() : bootstrapDylibPath(); - - // Read from launchctl inside the simulator (via simctl spawn) instead of - // `simctl getenv`. The latter silently truncates values longer than 127 bytes, - // which corrupts the colon-separated path list and causes stale entries to - // accumulate on every ensureEnv() cycle. - const result = await execFileAsync( - "xcrun", - ["simctl", "spawn", udid, "launchctl", "getenv", "DYLD_INSERT_LIBRARIES"], - { encoding: "utf8", timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ).catch((e) => ({ stdout: (e as NodeJS.ErrnoException & { stdout?: string }).stdout ?? "" })); - - const existing = (result.stdout ?? "").trim(); - const updated = buildDyldInsertLibraries(existing, bootstrapPath); - - if (updated !== existing) { - await execFileAsync( - "xcrun", - ["simctl", "spawn", udid, "launchctl", "setenv", "DYLD_INSERT_LIBRARIES", updated], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ); - } - - // Always re-set the endpoint env var — deterministic value, cheap no-op if already correct, - // ensures correctness after tool-server restarts. - if (endpoint.transport === "tcp") { - await execFileAsync( - "xcrun", - [ - "simctl", - "spawn", - udid, - "launchctl", - "setenv", - "NATIVE_DEVTOOLS_IOS_CDP_PORT", - String(endpoint.port), - ], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ); - } else { - await execFileAsync( - "xcrun", - [ - "simctl", - "spawn", - udid, - "launchctl", - "setenv", - "NATIVE_DEVTOOLS_IOS_CDP_SOCKET", - endpoint.socketPath, - ], - { timeout: SIMCTL_SPAWN_TIMEOUT_MS } - ); - } - - // Ensure the accessibility runtime is enabled so that describeScreen works on iOS 26+. - await ensureAccessibilityEnabled(udid); -} - -async function listRunningUIKitApplicationBundleIds(udid: string): Promise> { - const { stdout } = await execFileAsync("xcrun", ["simctl", "spawn", udid, "launchctl", "list"], { - encoding: "utf8", - }); - - const bundleIds = new Set(); - for (const line of stdout.split("\n")) { - const match = line.match(/UIKitApplication:([^\[]+)/); - if (match) { - bundleIds.add(match[1].trim()); - } - } - return bundleIds; -} - export const nativeDevtoolsBlueprint: ServiceBlueprint = { namespace: NATIVE_DEVTOOLS_NAMESPACE, @@ -352,20 +199,25 @@ export const nativeDevtoolsBlueprint: ServiceBlueprint(); const pendingRpc = new Map< @@ -401,7 +253,7 @@ export const nativeDevtoolsBlueprint: ServiceBlueprint ensureEnv(udid, endpoint)) + .then(() => host.setupNativeDevtoolsEnv(udid, endpoint)) .then(() => { envSetup = true; initFailure = null; @@ -418,7 +270,7 @@ export const nativeDevtoolsBlueprint: ServiceBlueprint => { - const runningBundleIds = await listRunningUIKitApplicationBundleIds(udid); + const runningBundleIds = await host.listRunningBundleIds(udid); return runningBundleIds.has(bundleId); }; @@ -542,8 +394,27 @@ export const nativeDevtoolsBlueprint: ServiceBlueprint((resolve, reject) => { + server.once("error", reject); + server.listen(endpoint.port ?? 0, "127.0.0.1", () => { + server.off("error", reject); + const addr = server.address(); + if (addr === null || typeof addr === "string") { + server.close(); + reject(new Error("native-devtools server failed to bind a TCP port")); + return; + } + endpoint.port = addr.port; + resolve(); + }); + }); + // Wire the reverse tunnel (no-op on local) before kicking off ensureEnv + // so the dylib's first dial — which can happen as soon as the env is + // written — lands on our listener. + await host.startProxy(udid, endpoint.port!); } else { server.listen(socketPath); } @@ -667,6 +538,9 @@ export const nativeDevtoolsBlueprint: ServiceBlueprint ` command). */ pressKey(direction: "Down" | "Up", keyCode: number): void; + /** + * Optional alternate transport. Set by the remote (MoQ) blueprint so that + * the shared `sendCommand` / `httpScreenshot` helpers in `simulator-client.ts` + * route through MoQ instead of WebSocket + HTTP. Undefined for local sims. + */ + transport?: import("../utils/simulator-client").SimulatorServerTransport; +} + +/** + * Build the SimulatorServerApi for an ios-remote device. The MoQ client + * connects to the remote simulator-server via WebTransport pinned to the + * fingerprint returned by `sim-remote moq-info`, and a MoQ-backed transport + * is attached so the shared `sendCommand` / `httpScreenshot` helpers route + * touch/screenshot/etc through MoQ instead of the local WS+HTTP path. + */ +async function buildRemoteInstance( + device: DeviceInfo +): Promise> { + const moq = await openMoqClient(device.id); + const events = new TypedEventEmitter(); + + const transport = createMoqTransport(moq, { + pasteText: async (text: string) => { + await simctlPbcopy(device.id, text); + // USB HID keyboard usage ids: 0xE3 = Left GUI (Cmd), 0x19 = V. + // Trigger Cmd+V on the remote sim to fire the actual paste. + const CMD = 0xe3; + const V = 0x19; + await moq.sendControl(encodeKey({ action: "Down", code: CMD })); + await moq.sendControl(encodeKey({ action: "Down", code: V })); + await moq.sendControl(encodeKey({ action: "Up", code: V })); + await moq.sendControl(encodeKey({ action: "Up", code: CMD })); + }, + }); + + // Local sims expose apiUrl/streamUrl as HTTP/WS endpoints; nothing remote + // analogue exists since input/screenshot/video are all in MoQ. Fill these + // with a tagged stub so the few places that read them log clearly instead + // of silently dialing a nonexistent local port. + const stubUrl = `moq+remote://${device.id}`; + + const api: SimulatorServerApi = { + apiUrl: stubUrl, + streamUrl: stubUrl, + pressKey: (direction, keyCode) => { + void moq.sendControl(encodeKey({ action: direction, code: keyCode })); + }, + transport, + }; + + return { + api, + dispose: async () => { + await moq.close(); + }, + events, + }; } function spawnSimulatorServerProcess( @@ -162,6 +223,10 @@ export const simulatorServerBlueprint: ServiceBlueprint {}); } else { diff --git a/packages/tool-server/src/proto/datachannel.proto b/packages/tool-server/src/proto/datachannel.proto new file mode 100644 index 00000000..7eae173f --- /dev/null +++ b/packages/tool-server/src/proto/datachannel.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; + +package datachannel; + +enum TouchAction { + TOUCH_DOWN = 0; + TOUCH_UP = 1; + TOUCH_MOVE = 2; +} + +enum KeyAction { + KEY_DOWN = 0; + KEY_UP = 1; +} + +enum ButtonType { + BUTTON_HOME = 0; + BUTTON_BACK = 1; + BUTTON_POWER = 2; + BUTTON_VOLUME_UP = 3; + BUTTON_VOLUME_DOWN = 4; + BUTTON_APP_SWITCH = 5; + BUTTON_ACTION = 6; +} + +enum RotateDirection { + ROTATE_PORTRAIT = 0; + ROTATE_PORTRAIT_UPSIDE_DOWN = 1; + ROTATE_LANDSCAPE_LEFT = 2; + ROTATE_LANDSCAPE_RIGHT = 3; +} + +message TouchCommand { + TouchAction action = 1; + double x = 2; + double y = 3; + optional double second_x = 4; + optional double second_y = 5; +} + +message KeyCommand { + KeyAction action = 1; + int32 code = 2; +} + +message ButtonCommand { + KeyAction action = 1; + ButtonType button = 2; +} + +message RotateCommand { + RotateDirection direction = 1; +} + +message WheelCommand { + double x = 1; + double y = 2; + double dx = 3; + double dy = 4; +} + +enum DownscalerType { + DOWNSCALER_LANCZOS3 = 0; + DOWNSCALER_BOX = 1; + DOWNSCALER_BILINEAR = 2; + DOWNSCALER_NEAREST = 3; +} + +message ScreenshotCommand { + optional string id = 1; + optional RotateDirection rotation = 2; + optional float scale = 3; + optional DownscalerType downscaler = 4; +} + +message DataChannelCommand { + oneof command { + TouchCommand touch = 1; + KeyCommand key = 2; + ButtonCommand button = 3; + RotateCommand rotate = 4; + WheelCommand wheel = 5; + ScreenshotCommand screenshot = 6; + } +} diff --git a/packages/tool-server/src/tools/button/index.ts b/packages/tool-server/src/tools/button/index.ts index 04e9c4b5..84ffd0c5 100644 --- a/packages/tool-server/src/tools/button/index.ts +++ b/packages/tool-server/src/tools/button/index.ts @@ -21,6 +21,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/describe/index.ts b/packages/tool-server/src/tools/describe/index.ts index 8997a202..d09483db 100644 --- a/packages/tool-server/src/tools/describe/index.ts +++ b/packages/tool-server/src/tools/describe/index.ts @@ -40,6 +40,7 @@ type Params = z.infer; const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -93,6 +94,14 @@ For React Native apps, debugger-component-tree returns React component names wit handler: async (_services, params) => withDescription(await describeAndroid(registry, params.udid, params.bundleId)), }, + iosRemote: { + // describeIos already handles both ax-service (TCP) and native-devtools + // fallback — both blueprints route through sim-remote when the device + // is ios-remote. Only the preflight dep differs. + requires: ["sim-remote"], + handler: async (_services, params, device) => + withDescription(await describeIos(registry, device, params)), + }, }), }; } diff --git a/packages/tool-server/src/tools/devices/boot-device.ts b/packages/tool-server/src/tools/devices/boot-device.ts index 0015bd3a..4a30e369 100644 --- a/packages/tool-server/src/tools/devices/boot-device.ts +++ b/packages/tool-server/src/tools/devices/boot-device.ts @@ -25,6 +25,14 @@ import { } from "../../utils/adb"; import { ensureDep } from "../../utils/check-deps"; import { listIosSimulators } from "../../utils/ios-devices"; +import { classifyDevice, stripRemotePrefix } from "../../utils/device-info"; +import { + simctlBoot as simRemoteBoot, + simctlBootstatus as simRemoteBootstatus, + simctlListDevices as simRemoteListDevices, + simctlShutdown as simRemoteShutdown, + setupAccessibilityDefaults as simRemoteSetupAccessibilityDefaults, +} from "../../utils/sim-remote"; const execFileAsync = promisify(execFile); @@ -67,6 +75,7 @@ type BootDeviceParams = z.infer; type BootDeviceResult = | { platform: "ios"; udid: string; booted: true } + | { platform: "ios-remote"; udid: string; booted: true } | { platform: "android"; serial: string; avdName: string; booted: true } | NativeDevtoolsInitFailedResult; @@ -300,6 +309,68 @@ async function bootIos( return { platform: "ios", udid, booted: true }; } +/** + * Boot a remote iOS simulator through `sim-remote`. Mirrors `bootIos` but: + * + * - Uses `sim-remote simctl` for boot/shutdown/bootstatus (no local xcrun). + * - Applies accessibility defaults via the orchestrator's + * `sim-remote setup accessibility-defaults` rather than the host pre-boot + * plist write (we have no filesystem access to the remote sim). + * - Pre-warms the native-devtools blueprint so the dylib injection env is + * set inside the remote sim before the app launches. + */ +async function bootIosRemote( + id: string, + registry: Registry, + force?: boolean +): Promise< + { platform: "ios-remote"; udid: string; booted: true } | NativeDevtoolsInitFailedResult +> { + await ensureDep("sim-remote"); + const udid = stripRemotePrefix(id); + + // Look up current state via sim-remote. Treat lookup failures as "unknown + // state" — the boot/bootstatus dance below tolerates an already-booted sim. + let simState: string | undefined; + try { + const list = await simRemoteListDevices(); + outer: for (const devices of Object.values(list.devices)) { + for (const d of devices) { + if (d.udid === udid) { + simState = d.state; + break outer; + } + } + } + } catch { + simState = undefined; + } + + if (force && simState === "Booted") { + await simRemoteShutdown(id).catch(() => undefined); + } + + // Boot. `Booted` exit-code error from sim-remote means it's already up — + // benign; the bootstatus call below normalizes the state regardless. + await simRemoteBoot(id).catch((err: Error) => { + if (!/Booted/i.test(err.message)) throw err; + }); + await simRemoteBootstatus(id, { boot: true }); + + // Idempotent: re-applies the three AX defaults every boot in case the + // orchestrator's simulator was wiped between sessions. + await simRemoteSetupAccessibilityDefaults(id).catch(() => undefined); + + const ndRef = nativeDevtoolsRef({ id, platform: "ios-remote", kind: "simulator" }); + const ndApi = await registry.resolveService(ndRef.urn, ndRef.options); + const initFailure = ndApi.getInitFailure(); + if (initFailure?.givenUp) { + return buildInitFailedResult(id, initFailure); + } + + return { platform: "ios-remote", udid: id, booted: true }; +} + // Tight budget for a hot boot attempt. A successful hot boot completes well // under 15 s on fast hardware and under ~45 s on a cold host page cache; the // 90 s ceiling exists to bound the pathological case where snapshot load @@ -770,6 +841,7 @@ function createEarlyExitRacer(getExit: () => Error | null): { // xcrun, etc., and so `list-devices` consumers can rely on uniform metadata. const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -795,6 +867,9 @@ Android boots take 2–10 minutes depending on machine and cold/warm state; the throw new Error("Provide exactly one of `udid` (iOS) or `avdName` (Android)."); } if (hasUdid) { + if (classifyDevice(params.udid!) === "ios-remote") { + return bootIosRemote(params.udid!, registry, params.force); + } return bootIos(params.udid!, registry, params.force); } return bootAndroid({ diff --git a/packages/tool-server/src/tools/devices/list-devices.ts b/packages/tool-server/src/tools/devices/list-devices.ts index 6057859e..7575c4e8 100644 --- a/packages/tool-server/src/tools/devices/list-devices.ts +++ b/packages/tool-server/src/tools/devices/list-devices.ts @@ -2,9 +2,19 @@ import { z } from "zod"; import type { ToolDefinition } from "@argent/registry"; import { listAndroidDevices, listAvds } from "../../utils/adb"; import { listIosSimulators, type IosSimulator } from "../../utils/ios-devices"; +import { simctlListDevices } from "../../utils/sim-remote"; +import { withRemotePrefix } from "../../utils/device-info"; type IosDevice = IosSimulator & { platform: "ios" }; +type IosRemoteDevice = { + platform: "ios-remote"; + udid: string; + name: string; + state: string; + runtime: string; +}; + type AndroidDevice = { platform: "android"; serial: string; @@ -16,7 +26,7 @@ type AndroidDevice = { }; type ListDevicesResult = { - devices: Array; + devices: Array; avds: Array<{ name: string }>; }; @@ -40,9 +50,42 @@ function sortAndroid(a: AndroidDevice, b: AndroidDevice): number { // Float booted/ready devices to the top of the merged list regardless of // platform — without this, all iOS entries are emitted before any Android. -function readinessRank(d: IosDevice | AndroidDevice): number { - if (d.platform === "ios") return d.state === "Booted" ? 0 : 1; - return d.state === "device" ? 0 : 1; +function readinessRank(d: IosDevice | IosRemoteDevice | AndroidDevice): number { + if (d.platform === "android") return d.state === "device" ? 0 : 1; + return d.state === "Booted" ? 0 : 1; +} + +/** + * List remote iOS simulators via `sim-remote`. Returns [] (silently) if + * sim-remote isn't installed or the user isn't logged in — list-devices + * already treats CLI absence as "platform unavailable" rather than failing. + */ +async function listRemoteIosSimulators(): Promise { + try { + const result = await simctlListDevices(); + const out: IosRemoteDevice[] = []; + for (const [runtime, devices] of Object.entries(result.devices)) { + for (const d of devices) { + if (d.isAvailable === false) continue; + out.push({ + platform: "ios-remote", + udid: withRemotePrefix(d.udid), + name: d.name, + state: d.state, + runtime, + }); + } + } + return out; + } catch { + return []; + } +} + +function sortIosRemote(a: IosRemoteDevice, b: IosRemoteDevice): number { + const aBooted = a.state === "Booted" ? 0 : 1; + const bBooted = b.state === "Booted" ? 0 : 1; + return aBooted - bBooted; } const zodSchema = z.object({}); @@ -58,13 +101,15 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si zodSchema, services: () => ({}), async execute(_services, _params) { - const [ios, android, avds] = await Promise.all([ + const [ios, iosRemote, android, avds] = await Promise.all([ listIosSimulators(), + listRemoteIosSimulators(), listAndroidDevices().catch(() => []), listAvds(), ]); const iosTagged: IosDevice[] = ios.map((s) => ({ platform: "ios", ...s })); iosTagged.sort(sortIos); + iosRemote.sort(sortIosRemote); const androidTagged: AndroidDevice[] = android.map((d) => ({ platform: "android", serial: d.serial, @@ -76,7 +121,11 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si })); androidTagged.sort(sortAndroid); - const devices: Array = [...iosTagged, ...androidTagged]; + const devices: Array = [ + ...iosTagged, + ...iosRemote, + ...androidTagged, + ]; devices.sort((a, b) => readinessRank(a) - readinessRank(b)); return { devices, avds }; diff --git a/packages/tool-server/src/tools/gesture-custom/index.ts b/packages/tool-server/src/tools/gesture-custom/index.ts index 06d95aa4..14ea892a 100644 --- a/packages/tool-server/src/tools/gesture-custom/index.ts +++ b/packages/tool-server/src/tools/gesture-custom/index.ts @@ -50,6 +50,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/gesture-pinch/index.ts b/packages/tool-server/src/tools/gesture-pinch/index.ts index 9de8dc01..5efbcee3 100644 --- a/packages/tool-server/src/tools/gesture-pinch/index.ts +++ b/packages/tool-server/src/tools/gesture-pinch/index.ts @@ -50,6 +50,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/gesture-rotate/index.ts b/packages/tool-server/src/tools/gesture-rotate/index.ts index b9233463..d93b75e6 100644 --- a/packages/tool-server/src/tools/gesture-rotate/index.ts +++ b/packages/tool-server/src/tools/gesture-rotate/index.ts @@ -40,6 +40,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/gesture-swipe/index.ts b/packages/tool-server/src/tools/gesture-swipe/index.ts index 27a80f6f..579e2158 100644 --- a/packages/tool-server/src/tools/gesture-swipe/index.ts +++ b/packages/tool-server/src/tools/gesture-swipe/index.ts @@ -27,6 +27,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/gesture-tap/index.ts b/packages/tool-server/src/tools/gesture-tap/index.ts index 528fc183..94656faa 100644 --- a/packages/tool-server/src/tools/gesture-tap/index.ts +++ b/packages/tool-server/src/tools/gesture-tap/index.ts @@ -21,6 +21,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/keyboard/index.ts b/packages/tool-server/src/tools/keyboard/index.ts index ee0309f5..5461de32 100644 --- a/packages/tool-server/src/tools/keyboard/index.ts +++ b/packages/tool-server/src/tools/keyboard/index.ts @@ -32,6 +32,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/launch-app/index.ts b/packages/tool-server/src/tools/launch-app/index.ts index ff6cf4c6..3c9f3119 100644 --- a/packages/tool-server/src/tools/launch-app/index.ts +++ b/packages/tool-server/src/tools/launch-app/index.ts @@ -6,6 +6,7 @@ import { resolveDevice } from "../../utils/device-info"; import type { LaunchAppAndroidServices, LaunchAppIosServices, LaunchAppResult } from "./types"; import { iosImpl } from "./platforms/ios"; import { androidImpl } from "./platforms/android"; +import { iosRemoteImpl } from "./platforms/ios-remote"; // Android package grammar is `[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+`; // iOS bundle ids use the same reverse-DNS shape with dashes allowed. The union @@ -43,6 +44,7 @@ type Params = z.infer; const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -62,7 +64,10 @@ Common Android packages: com.android.settings, com.android.chrome, com.google.an // Resolving it on Android would force the iOS-only blueprint to spin up. services: (params): Record => { const device = resolveDevice(params.udid); - return device.platform === "ios" ? { nativeDevtools: nativeDevtoolsRef(device) } : {}; + if (device.platform === "ios" || device.platform === "ios-remote") { + return { nativeDevtools: nativeDevtoolsRef(device) }; + } + return {}; }, execute: dispatchByPlatform< LaunchAppIosServices, @@ -74,5 +79,6 @@ Common Android packages: com.android.settings, com.android.chrome, com.google.an capability, ios: iosImpl, android: androidImpl, + iosRemote: iosRemoteImpl, }), }; diff --git a/packages/tool-server/src/tools/launch-app/platforms/ios-remote.ts b/packages/tool-server/src/tools/launch-app/platforms/ios-remote.ts new file mode 100644 index 00000000..be1c629d --- /dev/null +++ b/packages/tool-server/src/tools/launch-app/platforms/ios-remote.ts @@ -0,0 +1,13 @@ +import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { remoteSimctl } from "../../../utils/simctl-backend"; +import type { LaunchAppIosServices, LaunchAppParams, LaunchAppResult } from "../types"; +import { buildIosLaunchHandler } from "./shared"; + +/** + * Remote analogue of `iosImpl`. Routes through `sim-remote simctl launch` + * instead of `xcrun simctl launch`; the native-devtools precheck is shared. + */ +export const iosRemoteImpl: PlatformImpl = { + requires: ["sim-remote"], + handler: buildIosLaunchHandler(remoteSimctl), +}; diff --git a/packages/tool-server/src/tools/launch-app/platforms/ios.ts b/packages/tool-server/src/tools/launch-app/platforms/ios.ts index ddc5814c..f6acc671 100644 --- a/packages/tool-server/src/tools/launch-app/platforms/ios.ts +++ b/packages/tool-server/src/tools/launch-app/platforms/ios.ts @@ -1,17 +1,9 @@ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; -import { precheckNativeDevtools } from "../../../blueprints/native-devtools"; import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { localSimctl } from "../../../utils/simctl-backend"; import type { LaunchAppIosServices, LaunchAppParams, LaunchAppResult } from "../types"; - -const execFileAsync = promisify(execFile); +import { buildIosLaunchHandler } from "./shared"; export const iosImpl: PlatformImpl = { requires: ["xcrun"], - handler: async (services, params) => { - const blocked = await precheckNativeDevtools(services.nativeDevtools, params.udid); - if (blocked) return blocked; - await execFileAsync("xcrun", ["simctl", "launch", params.udid, params.bundleId]); - return { launched: true, bundleId: params.bundleId }; - }, + handler: buildIosLaunchHandler(localSimctl), }; diff --git a/packages/tool-server/src/tools/launch-app/platforms/shared.ts b/packages/tool-server/src/tools/launch-app/platforms/shared.ts new file mode 100644 index 00000000..8d5dc75a --- /dev/null +++ b/packages/tool-server/src/tools/launch-app/platforms/shared.ts @@ -0,0 +1,21 @@ +import { precheckNativeDevtools } from "../../../blueprints/native-devtools"; +import type { SimctlBackend } from "../../../utils/simctl-backend"; +import type { LaunchAppIosServices, LaunchAppParams, LaunchAppResult } from "../types"; + +/** + * Shared iOS handler used by both the local (`xcrun simctl`) and remote + * (`sim-remote simctl`) branches. The native-devtools precheck and the + * launched-payload shape are identical between the two; only the simctl + * verbs differ — parametrised via `backend`. + */ +export function buildIosLaunchHandler(backend: SimctlBackend) { + return async ( + services: LaunchAppIosServices, + params: LaunchAppParams + ): Promise => { + const blocked = await precheckNativeDevtools(services.nativeDevtools, params.udid); + if (blocked) return blocked; + await backend.launch(params.udid, params.bundleId); + return { launched: true, bundleId: params.bundleId }; + }; +} diff --git a/packages/tool-server/src/tools/native-devtools/native-describe-screen.ts b/packages/tool-server/src/tools/native-devtools/native-describe-screen.ts index d0fc98e6..b12d97a3 100644 --- a/packages/tool-server/src/tools/native-devtools/native-describe-screen.ts +++ b/packages/tool-server/src/tools/native-devtools/native-describe-screen.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; import { parseNativeDescribeScreenResult, type NativeDescribeScreenResult, @@ -40,8 +41,7 @@ type Result = export const nativeDescribeScreenTool: ToolDefinition = { id: "native-describe-screen", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Read the running app's native accessibility screen description via injected native devtools. Returns a flat list of accessibility leaf elements with: @@ -61,6 +61,9 @@ If status is restart_required: call restart-app then retry.`, nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/native-devtools/native-devtools-status.ts b/packages/tool-server/src/tools/native-devtools/native-devtools-status.ts index 5d53529a..75cf0347 100644 --- a/packages/tool-server/src/tools/native-devtools/native-devtools-status.ts +++ b/packages/tool-server/src/tools/native-devtools/native-devtools-status.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -26,8 +27,7 @@ type Result = export const nativeDevtoolsStatusTool: ToolDefinition = { id: "native-devtools-status", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Check whether native devtools are connected to a specific app and whether the next launch is prepared for injection. Use when you need to verify native devtools readiness before calling native-full-hierarchy, native-describe-screen, or native-network-logs. @@ -47,6 +47,9 @@ Fails if the simulator server is not running for the given UDID or the bundleId nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid); diff --git a/packages/tool-server/src/tools/native-devtools/native-find-views.ts b/packages/tool-server/src/tools/native-devtools/native-find-views.ts index fe5e7467..2e03244b 100644 --- a/packages/tool-server/src/tools/native-devtools/native-find-views.ts +++ b/packages/tool-server/src/tools/native-devtools/native-find-views.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -43,8 +44,7 @@ type Result = export const nativeFindViewsTool: ToolDefinition = { id: "native-find-views", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Search for specific UIViews in the running app by class name, accessibility identifier, label, tag, or React Native nativeID. Use when you need to locate a specific view by its properties without dumping the entire hierarchy. Returns { status: "ok", matches } with matching views including their frames, properties, optional ancestors, and optional children. Much more targeted than native-full-hierarchy. @@ -55,6 +55,9 @@ Fails if native devtools are not connected, the app is not running, or status is nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/native-devtools/native-full-hierarchy.ts b/packages/tool-server/src/tools/native-devtools/native-full-hierarchy.ts index b44ec811..ab85f7f0 100644 --- a/packages/tool-server/src/tools/native-devtools/native-full-hierarchy.ts +++ b/packages/tool-server/src/tools/native-devtools/native-full-hierarchy.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -57,8 +58,7 @@ type Result = export const nativeFullHierarchyTool: ToolDefinition = { id: "native-full-hierarchy", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Get the complete UIKit view tree for the running app. WARNING: Output can be extremely large (100KB–500KB+) for complex apps, especially those built with SwiftUI. Prefer native-find-views for targeted queries. Use skipClasses / skipClassPrefixes to prune SwiftUI internal subtrees and reduce output size. Use the fields param to request only the properties you need. @@ -70,6 +70,9 @@ Fails if native devtools are not connected or the app is not running.`, nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/native-devtools/native-network-logs.ts b/packages/tool-server/src/tools/native-devtools/native-network-logs.ts index adc7ccf6..f8cc9fd0 100644 --- a/packages/tool-server/src/tools/native-devtools/native-network-logs.ts +++ b/packages/tool-server/src/tools/native-devtools/native-network-logs.ts @@ -8,6 +8,7 @@ import { type NetworkEvent, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -28,8 +29,7 @@ type Result = export const nativeNetworkLogsTool: ToolDefinition = { id: "native-network-logs", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Retrieve network requests captured at the native NSURLProtocol level. Unlike the JS-level network inspector (view-network-logs), this captures ALL network traffic from the app including native modules, Swift/Objective-C networking, and background transfers that bypass JS fetch. Use when you need to inspect native-level HTTP traffic that is invisible to JS fetch interception. @@ -40,6 +40,9 @@ Fails if native devtools are not connected or the app is not running.`, nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/native-devtools/native-user-interactable-view-at-point.ts b/packages/tool-server/src/tools/native-devtools/native-user-interactable-view-at-point.ts index 8ea724ce..f4e9701f 100644 --- a/packages/tool-server/src/tools/native-devtools/native-user-interactable-view-at-point.ts +++ b/packages/tool-server/src/tools/native-devtools/native-user-interactable-view-at-point.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -62,8 +63,7 @@ type Result = export const nativeUserInteractableViewAtPointTool: ToolDefinition = { id: "native-user-interactable-view-at-point", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Inspect the deepest UIView at a raw native window point that would actually receive touch input. Unlike native-view-at-point, this respects userInteractionEnabled and is closer to @@ -78,6 +78,9 @@ If status is restart_required: call restart-app then retry.`, nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/native-devtools/native-view-at-point.ts b/packages/tool-server/src/tools/native-devtools/native-view-at-point.ts index 9242a023..f191df76 100644 --- a/packages/tool-server/src/tools/native-devtools/native-view-at-point.ts +++ b/packages/tool-server/src/tools/native-devtools/native-view-at-point.ts @@ -7,6 +7,7 @@ import { type NativeDevtoolsInitFailedResult, } from "../../blueprints/native-devtools"; import { resolveDevice } from "../../utils/device-info"; +import { ensureDeps } from "../../utils/check-deps"; const zodSchema = z.object({ udid: z.string().describe("Simulator UDID"), @@ -62,8 +63,7 @@ type Result = export const nativeViewAtPointTool: ToolDefinition = { id: "native-view-at-point", - requires: ["xcrun"], - capability: { apple: { simulator: true, device: true } }, + capability: { apple: { simulator: true, device: true }, appleRemote: { simulator: true } }, description: `Inspect the deepest visible UIView at a raw native window point. Unlike native-user-interactable-view-at-point, this ignores userInteractionEnabled, @@ -78,6 +78,9 @@ If status is restart_required: call restart-app then retry.`, nativeDevtools: nativeDevtoolsRef(resolveDevice(params.udid)), }), async execute(services, params) { + const device = resolveDevice(params.udid); + await ensureDeps(device.platform === "ios-remote" ? ["sim-remote"] : ["xcrun"]); + const api = services.nativeDevtools as NativeDevtoolsApi; const blocked = await precheckNativeDevtools(api, params.udid, params.bundleId); diff --git a/packages/tool-server/src/tools/open-url/index.ts b/packages/tool-server/src/tools/open-url/index.ts index 8023cadd..3face914 100644 --- a/packages/tool-server/src/tools/open-url/index.ts +++ b/packages/tool-server/src/tools/open-url/index.ts @@ -4,6 +4,7 @@ import { dispatchByPlatform } from "../../utils/cross-platform-tool"; import type { OpenUrlResult, OpenUrlServices } from "./types"; import { iosImpl } from "./platforms/ios"; import { androidImpl } from "./platforms/android"; +import { iosRemoteImpl } from "./platforms/ios-remote"; const zodSchema = z.object({ udid: z @@ -21,6 +22,7 @@ type Params = z.infer; const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -38,5 +40,6 @@ Returns { opened, url }. Fails if no app is registered to handle the URI.`, capability, ios: iosImpl, android: androidImpl, + iosRemote: iosRemoteImpl, }), }; diff --git a/packages/tool-server/src/tools/open-url/platforms/ios-remote.ts b/packages/tool-server/src/tools/open-url/platforms/ios-remote.ts new file mode 100644 index 00000000..9d92bb38 --- /dev/null +++ b/packages/tool-server/src/tools/open-url/platforms/ios-remote.ts @@ -0,0 +1,11 @@ +import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { simctlOpenUrl } from "../../../utils/sim-remote"; +import type { OpenUrlParams, OpenUrlResult, OpenUrlServices } from "../types"; + +export const iosRemoteImpl: PlatformImpl = { + requires: ["sim-remote"], + handler: async (_services, params) => { + await simctlOpenUrl(params.udid, params.url); + return { opened: true, url: params.url }; + }, +}; diff --git a/packages/tool-server/src/tools/paste/index.ts b/packages/tool-server/src/tools/paste/index.ts index 0ec050cb..c0ec32e2 100644 --- a/packages/tool-server/src/tools/paste/index.ts +++ b/packages/tool-server/src/tools/paste/index.ts @@ -20,6 +20,7 @@ interface Result { // itself is iOS-only — no platforms/ split needed. const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, }; export const pasteTool: ToolDefinition = { diff --git a/packages/tool-server/src/tools/reinstall-app/index.ts b/packages/tool-server/src/tools/reinstall-app/index.ts index 1f4b6b2a..131158f5 100644 --- a/packages/tool-server/src/tools/reinstall-app/index.ts +++ b/packages/tool-server/src/tools/reinstall-app/index.ts @@ -4,6 +4,7 @@ import { dispatchByPlatform } from "../../utils/cross-platform-tool"; import type { ReinstallAppResult, ReinstallAppServices } from "./types"; import { iosImpl } from "./platforms/ios"; import { androidImpl } from "./platforms/android"; +import { iosRemoteImpl } from "./platforms/ios-remote"; const zodSchema = z.object({ udid: z @@ -26,6 +27,7 @@ type Params = z.infer; const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -47,5 +49,6 @@ Returns { reinstalled, bundleId }. Fails if the app path does not exist or the p capability, ios: iosImpl, android: androidImpl, + iosRemote: iosRemoteImpl, }), }; diff --git a/packages/tool-server/src/tools/reinstall-app/platforms/ios-remote.ts b/packages/tool-server/src/tools/reinstall-app/platforms/ios-remote.ts new file mode 100644 index 00000000..69c13738 --- /dev/null +++ b/packages/tool-server/src/tools/reinstall-app/platforms/ios-remote.ts @@ -0,0 +1,29 @@ +import { resolve as resolvePath } from "node:path"; +import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { simctlInstall, simctlUninstall } from "../../../utils/sim-remote"; +import type { ReinstallAppParams, ReinstallAppResult, ReinstallAppServices } from "../types"; + +/** + * Remote analogue of the iOS impl. `sim-remote simctl install` uploads the + * local `.app` to the orchestrator over QUIC, so this works against a remote + * sim with no extra staging — the developer points at the same on-disk path + * they'd use locally. + */ +export const iosRemoteImpl: PlatformImpl< + ReinstallAppServices, + ReinstallAppParams, + ReinstallAppResult +> = { + requires: ["sim-remote"], + handler: async (_services, params) => { + const { udid, bundleId, appPath } = params; + const absolute = resolvePath(appPath); + try { + await simctlUninstall(udid, bundleId); + } catch { + // App may not be installed — continue to install. + } + await simctlInstall(udid, absolute); + return { reinstalled: true, bundleId }; + }, +}; diff --git a/packages/tool-server/src/tools/restart-app/index.ts b/packages/tool-server/src/tools/restart-app/index.ts index 76767393..f11ff596 100644 --- a/packages/tool-server/src/tools/restart-app/index.ts +++ b/packages/tool-server/src/tools/restart-app/index.ts @@ -6,6 +6,7 @@ import { resolveDevice } from "../../utils/device-info"; import type { RestartAppAndroidServices, RestartAppIosServices, RestartAppResult } from "./types"; import { iosImpl } from "./platforms/ios"; import { androidImpl } from "./platforms/android"; +import { iosRemoteImpl } from "./platforms/ios-remote"; // Bundle id / package name. Head must be letter or underscore so a bundleId // like `--user` can't masquerade as a flag inside `am force-stop …`. @@ -38,6 +39,7 @@ type Params = z.infer; const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; @@ -53,7 +55,10 @@ Returns { restarted, bundleId }. Fails if the app is not installed.`, // Only iOS needs the native-devtools service for relaunch injection. services: (params): Record => { const device = resolveDevice(params.udid); - return device.platform === "ios" ? { nativeDevtools: nativeDevtoolsRef(device) } : {}; + if (device.platform === "ios" || device.platform === "ios-remote") { + return { nativeDevtools: nativeDevtoolsRef(device) }; + } + return {}; }, execute: dispatchByPlatform< RestartAppIosServices, @@ -65,5 +70,6 @@ Returns { restarted, bundleId }. Fails if the app is not installed.`, capability, ios: iosImpl, android: androidImpl, + iosRemote: iosRemoteImpl, }), }; diff --git a/packages/tool-server/src/tools/restart-app/platforms/ios-remote.ts b/packages/tool-server/src/tools/restart-app/platforms/ios-remote.ts new file mode 100644 index 00000000..3bff600f --- /dev/null +++ b/packages/tool-server/src/tools/restart-app/platforms/ios-remote.ts @@ -0,0 +1,13 @@ +import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { remoteSimctl } from "../../../utils/simctl-backend"; +import type { RestartAppIosServices, RestartAppParams, RestartAppResult } from "../types"; +import { buildIosRestartHandler } from "./shared"; + +export const iosRemoteImpl: PlatformImpl< + RestartAppIosServices, + RestartAppParams, + RestartAppResult +> = { + requires: ["sim-remote"], + handler: buildIosRestartHandler(remoteSimctl), +}; diff --git a/packages/tool-server/src/tools/restart-app/platforms/ios.ts b/packages/tool-server/src/tools/restart-app/platforms/ios.ts index a02e7050..0da42662 100644 --- a/packages/tool-server/src/tools/restart-app/platforms/ios.ts +++ b/packages/tool-server/src/tools/restart-app/platforms/ios.ts @@ -1,23 +1,9 @@ -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; -import { precheckNativeDevtools } from "../../../blueprints/native-devtools"; import type { PlatformImpl } from "../../../utils/cross-platform-tool"; +import { localSimctl } from "../../../utils/simctl-backend"; import type { RestartAppIosServices, RestartAppParams, RestartAppResult } from "../types"; - -const execFileAsync = promisify(execFile); +import { buildIosRestartHandler } from "./shared"; export const iosImpl: PlatformImpl = { requires: ["xcrun"], - handler: async (services, params) => { - const { udid, bundleId } = params; - const blocked = await precheckNativeDevtools(services.nativeDevtools, udid); - if (blocked) return blocked; - try { - await execFileAsync("xcrun", ["simctl", "terminate", udid, bundleId]); - } catch { - // App may not be running — ignore - } - await execFileAsync("xcrun", ["simctl", "launch", udid, bundleId]); - return { restarted: true, bundleId }; - }, + handler: buildIosRestartHandler(localSimctl), }; diff --git a/packages/tool-server/src/tools/restart-app/platforms/shared.ts b/packages/tool-server/src/tools/restart-app/platforms/shared.ts new file mode 100644 index 00000000..ca60f4a8 --- /dev/null +++ b/packages/tool-server/src/tools/restart-app/platforms/shared.ts @@ -0,0 +1,27 @@ +import { precheckNativeDevtools } from "../../../blueprints/native-devtools"; +import type { SimctlBackend } from "../../../utils/simctl-backend"; +import type { RestartAppIosServices, RestartAppParams, RestartAppResult } from "../types"; + +/** + * Shared iOS handler for both local (`xcrun simctl`) and remote + * (`sim-remote simctl`) branches. Termination is best-effort — the app may not + * be running. Only the simctl verbs differ between branches, parametrised via + * `backend`. + */ +export function buildIosRestartHandler(backend: SimctlBackend) { + return async ( + services: RestartAppIosServices, + params: RestartAppParams + ): Promise => { + const { udid, bundleId } = params; + const blocked = await precheckNativeDevtools(services.nativeDevtools, udid); + if (blocked) return blocked; + try { + await backend.terminate(udid, bundleId); + } catch { + // App may not be running — ignore. + } + await backend.launch(udid, bundleId); + return { restarted: true, bundleId }; + }; +} diff --git a/packages/tool-server/src/tools/rotate/index.ts b/packages/tool-server/src/tools/rotate/index.ts index 263bd3d6..840f8f00 100644 --- a/packages/tool-server/src/tools/rotate/index.ts +++ b/packages/tool-server/src/tools/rotate/index.ts @@ -19,6 +19,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/run-sequence/index.ts b/packages/tool-server/src/tools/run-sequence/index.ts index fa2708eb..b768af82 100644 --- a/packages/tool-server/src/tools/run-sequence/index.ts +++ b/packages/tool-server/src/tools/run-sequence/index.ts @@ -61,6 +61,7 @@ type RunSequenceResult = { // failure mode is consistent. const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/tools/screenshot/index.ts b/packages/tool-server/src/tools/screenshot/index.ts index d2c173f0..be6fc901 100644 --- a/packages/tool-server/src/tools/screenshot/index.ts +++ b/packages/tool-server/src/tools/screenshot/index.ts @@ -36,6 +36,7 @@ interface Result { const capability: ToolCapability = { apple: { simulator: true, device: true }, + appleRemote: { simulator: true }, android: { emulator: true, device: true, unknown: true }, }; diff --git a/packages/tool-server/src/utils/ax-prefs.ts b/packages/tool-server/src/utils/ax-prefs.ts new file mode 100644 index 00000000..cca13ae3 --- /dev/null +++ b/packages/tool-server/src/utils/ax-prefs.ts @@ -0,0 +1,105 @@ +import * as fsAsync from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { SIMCTL_SPAWN_TIMEOUT_MS } from "./simctl-config"; + +const execFileAsync = promisify(execFile); + +export async function ensureAutomationEnabled(udid: string): Promise { + await execFileAsync( + "xcrun", + [ + "simctl", + "spawn", + udid, + "defaults", + "write", + "com.apple.Accessibility", + "AutomationEnabled", + "-bool", + "true", + ], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ); +} + +/** + * Check whether `IgnoreAXServerEntitlements` is active on this sim. + * + * iOS 26.5+: SB's AX server rejects unentitled MIG clients with + * kAXError -25215. The pref disables the check, but SB caches it at + * init — writing it post-boot has no effect until the next restart. + * The only effective path is the pre-boot plist write in boot-device. + * + * This read-only probe tells the caller whether the pre-boot write + * happened so describe can surface a degraded-quality hint when it didn't. + */ +export async function isEntitlementBypassActive(udid: string): Promise { + return execFileAsync( + "xcrun", + [ + "simctl", + "spawn", + udid, + "defaults", + "read", + "com.apple.Accessibility", + "IgnoreAXServerEntitlements", + ], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ) + .then(({ stdout }) => stdout.trim() === "1") + .catch(() => false); +} + +/** + * Host-side `com.apple.Accessibility` plist inside the sim's data container. + * Writeable while Shutdown; in-sim cfprefsd overwrites it once Booted. + */ +function accessibilityPlistPath(udid: string): string { + return path.join( + os.homedir(), + "Library/Developer/CoreSimulator/Devices", + udid, + "data/Library/Preferences/com.apple.Accessibility.plist" + ); +} + +/** + * Write the four AX prefs to the sim's host plist BEFORE `simctl boot` so SB + * caches them at AX-server init and never needs the disruptive kickstart + * (which kills the foreground app and dismisses in-flight system alerts). + * + * All four are required on a freshly-erased sim: + * - `IgnoreAXServerEntitlements` bypasses the iOS 26.5+ kAXErrorNotEntitled check. + * - `AutomationEnabled` opts the simctl-spawned ax-service in as an AX client. + * - `AccessibilityEnabled` + `ApplicationAccessibilityEnabled` gate the AT + * subsystem bootstrap. Without them SB never spawns `AccessibilityUIServer` + * and describe returns an empty ROOT even though the entitlement check passes + * (reproduced on a wiped iPhone 17e: AccessibilityUIServer active count = 0 + * without these two; auto-spawns at boot with them). + * + * Caller must ensure the sim is Shutdown — in-sim cfprefsd would otherwise + * overwrite this file on flush. + */ +export async function setAccessibilityPrefsPreBoot(udid: string): Promise { + const plistPath = accessibilityPlistPath(udid); + await fsAsync.mkdir(path.dirname(plistPath), { recursive: true }); + const exists = await fsAsync + .access(plistPath) + .then(() => true) + .catch(() => false); + if (!exists) { + await execFileAsync("plutil", ["-create", "binary1", plistPath]); + } + for (const key of [ + "AutomationEnabled", + "IgnoreAXServerEntitlements", + "AccessibilityEnabled", + "ApplicationAccessibilityEnabled", + ]) { + await execFileAsync("plutil", ["-replace", key, "-bool", "true", plistPath]); + } +} diff --git a/packages/tool-server/src/utils/capability.ts b/packages/tool-server/src/utils/capability.ts index db29092a..c2d5b892 100644 --- a/packages/tool-server/src/utils/capability.ts +++ b/packages/tool-server/src/utils/capability.ts @@ -71,7 +71,12 @@ export function assertSupported( device: DeviceInfo ): void { if (!capability) return; - const matrix = device.platform === "ios" ? capability.apple : capability.android; + const matrix = + device.platform === "ios" + ? capability.apple + : device.platform === "ios-remote" + ? capability.appleRemote + : capability.android; if (!matrix) { throw new UnsupportedOperationError(toolId, device, `no ${device.platform} support declared`); } diff --git a/packages/tool-server/src/utils/check-deps.ts b/packages/tool-server/src/utils/check-deps.ts index 53e729d0..4c5e2d06 100644 --- a/packages/tool-server/src/utils/check-deps.ts +++ b/packages/tool-server/src/utils/check-deps.ts @@ -31,11 +31,14 @@ const cache = new Map(); // Short per-dep hints — the message is what the LLM sees on a missing-dep // error, so it should tell it how to unblock the user. const INSTALL_HINTS: Record = { - xcrun: + "xcrun": "Xcode command-line tools are not installed. Run `xcode-select --install` (or install Xcode from the App Store) and retry. Only required for iOS simulators.", - adb: "Android SDK Platform Tools not found. Install with `brew install --cask android-platform-tools` or via Android Studio → SDK Manager. If installed, ensure `adb` is on PATH or set `$ANDROID_HOME` to the SDK root (the resolver checks `$ANDROID_HOME/platform-tools/adb`). Only required for Android devices and emulators.", - emulator: + "adb": + "Android SDK Platform Tools not found. Install with `brew install --cask android-platform-tools` or via Android Studio → SDK Manager. If installed, ensure `adb` is on PATH or set `$ANDROID_HOME` to the SDK root (the resolver checks `$ANDROID_HOME/platform-tools/adb`). Only required for Android devices and emulators.", + "emulator": "Android Emulator not found. Install via Android Studio → SDK Manager → Emulator, or `sdkmanager 'emulator'`. If installed, ensure `emulator` is on PATH or set `$ANDROID_HOME` to the SDK root (the resolver checks `$ANDROID_HOME/emulator/emulator`). Only required to launch new Android emulators via `boot-device`.", + "sim-remote": + "`sim-remote` CLI not found on PATH. Install via the radon-cloud project (see its README) and run `sim-remote login` before invoking any ios-remote tool. Only required for remote iOS simulators.", }; async function probe(dep: ToolDependency): Promise { diff --git a/packages/tool-server/src/utils/cross-platform-tool.ts b/packages/tool-server/src/utils/cross-platform-tool.ts index 6f922c7a..cd5043e5 100644 --- a/packages/tool-server/src/utils/cross-platform-tool.ts +++ b/packages/tool-server/src/utils/cross-platform-tool.ts @@ -52,11 +52,19 @@ export function dispatchByPlatform< AndroidServices, Params extends { udid: string }, Result, + IosRemoteServices = IosServices, >(opts: { toolId: string; capability: ToolCapability; ios: PlatformImpl; android: PlatformImpl; + /** + * Optional ios-remote branch. When omitted, an ios-remote device will hit + * `assertSupported` and fail there if the tool's capability matrix doesn't + * include `appleRemote` — so adding ios-remote support is two changes (this + * branch + the matrix), and the absence of either is a clean 400. + */ + iosRemote?: PlatformImpl; }): ( services: Record, params: Params, @@ -71,6 +79,23 @@ export function dispatchByPlatform< } return opts.ios.handler(services as unknown as IosServices, params, device, invokeOptions); } + if (device.platform === "ios-remote") { + if (!opts.iosRemote) { + throw new Error( + `Tool '${opts.toolId}' declares ios-remote capability but has no iosRemote branch. ` + + `Add an iosRemote PlatformImpl to dispatchByPlatform().` + ); + } + if (opts.iosRemote.requires?.length) { + await ensureDeps(opts.iosRemote.requires); + } + return opts.iosRemote.handler( + services as unknown as IosRemoteServices, + params, + device, + invokeOptions + ); + } if (opts.android.requires?.length) { await ensureDeps(opts.android.requires); } diff --git a/packages/tool-server/src/utils/datachannel-proto.ts b/packages/tool-server/src/utils/datachannel-proto.ts new file mode 100644 index 00000000..f00a736f --- /dev/null +++ b/packages/tool-server/src/utils/datachannel-proto.ts @@ -0,0 +1,200 @@ +import protobuf from "protobufjs"; + +/** + * Vendored copy of `radon-cloud/packages/simulator-server/proto/datachannel.proto`. + * Kept inline so we don't need a build step to ship the .proto file alongside + * the compiled tool-server. If you bump the simulator-server schema, regenerate + * this string from the canonical .proto. + */ +const PROTO_SOURCE = ` +syntax = "proto3"; + +package datachannel; + +enum TouchAction { + TOUCH_DOWN = 0; + TOUCH_UP = 1; + TOUCH_MOVE = 2; +} + +enum KeyAction { + KEY_DOWN = 0; + KEY_UP = 1; +} + +enum ButtonType { + BUTTON_HOME = 0; + BUTTON_BACK = 1; + BUTTON_POWER = 2; + BUTTON_VOLUME_UP = 3; + BUTTON_VOLUME_DOWN = 4; + BUTTON_APP_SWITCH = 5; + BUTTON_ACTION = 6; +} + +enum RotateDirection { + ROTATE_PORTRAIT = 0; + ROTATE_PORTRAIT_UPSIDE_DOWN = 1; + ROTATE_LANDSCAPE_LEFT = 2; + ROTATE_LANDSCAPE_RIGHT = 3; +} + +message TouchCommand { + TouchAction action = 1; + double x = 2; + double y = 3; + optional double second_x = 4; + optional double second_y = 5; +} + +message KeyCommand { + KeyAction action = 1; + int32 code = 2; +} + +message ButtonCommand { + KeyAction action = 1; + ButtonType button = 2; +} + +message RotateCommand { + RotateDirection direction = 1; +} + +message WheelCommand { + double x = 1; + double y = 2; + double dx = 3; + double dy = 4; +} + +enum DownscalerType { + DOWNSCALER_LANCZOS3 = 0; + DOWNSCALER_BOX = 1; + DOWNSCALER_BILINEAR = 2; + DOWNSCALER_NEAREST = 3; +} + +message ScreenshotCommand { + optional string id = 1; + optional RotateDirection rotation = 2; + optional float scale = 3; + optional DownscalerType downscaler = 4; +} + +message DataChannelCommand { + oneof command { + TouchCommand touch = 1; + KeyCommand key = 2; + ButtonCommand button = 3; + RotateCommand rotate = 4; + WheelCommand wheel = 5; + ScreenshotCommand screenshot = 6; + } +} +`; + +const root = protobuf.parse(PROTO_SOURCE, { keepCase: false }).root; +const DataChannelCommand = root.lookupType("datachannel.DataChannelCommand"); + +// Enum value lookups, named by their enum identifier inside the .proto so the +// public API can be friendly strings without leaking protobufjs internals. + +const TouchAction = root.lookupEnum("datachannel.TouchAction").values; +const KeyAction = root.lookupEnum("datachannel.KeyAction").values; +const ButtonType = root.lookupEnum("datachannel.ButtonType").values; +const RotateDirection = root.lookupEnum("datachannel.RotateDirection").values; + +export type TouchActionName = "Down" | "Up" | "Move"; +export type KeyActionName = "Down" | "Up"; +export type ButtonName = + | "home" + | "back" + | "power" + | "volumeUp" + | "volumeDown" + | "appSwitch" + | "actionButton"; +export type RotationName = "Portrait" | "PortraitUpsideDown" | "LandscapeLeft" | "LandscapeRight"; + +const TOUCH_ACTION: Record = { + Down: TouchAction.TOUCH_DOWN ?? 0, + Up: TouchAction.TOUCH_UP ?? 1, + Move: TouchAction.TOUCH_MOVE ?? 2, +}; + +const KEY_ACTION: Record = { + Down: KeyAction.KEY_DOWN ?? 0, + Up: KeyAction.KEY_UP ?? 1, +}; + +const BUTTON_TYPE: Record = { + home: ButtonType.BUTTON_HOME ?? 0, + back: ButtonType.BUTTON_BACK ?? 1, + power: ButtonType.BUTTON_POWER ?? 2, + volumeUp: ButtonType.BUTTON_VOLUME_UP ?? 3, + volumeDown: ButtonType.BUTTON_VOLUME_DOWN ?? 4, + appSwitch: ButtonType.BUTTON_APP_SWITCH ?? 5, + actionButton: ButtonType.BUTTON_ACTION ?? 6, +}; + +const ROTATION: Record = { + Portrait: RotateDirection.ROTATE_PORTRAIT ?? 0, + PortraitUpsideDown: RotateDirection.ROTATE_PORTRAIT_UPSIDE_DOWN ?? 1, + LandscapeLeft: RotateDirection.ROTATE_LANDSCAPE_LEFT ?? 2, + LandscapeRight: RotateDirection.ROTATE_LANDSCAPE_RIGHT ?? 3, +}; + +function encode(payload: Record): Uint8Array { + const message = DataChannelCommand.create(payload); + return DataChannelCommand.encode(message).finish(); +} + +export function encodeTouch(opts: { + action: TouchActionName; + x: number; + y: number; + secondX?: number; + secondY?: number; +}): Uint8Array { + const touch: Record = { + action: TOUCH_ACTION[opts.action], + x: opts.x, + y: opts.y, + }; + if (opts.secondX !== undefined) touch.secondX = opts.secondX; + if (opts.secondY !== undefined) touch.secondY = opts.secondY; + return encode({ touch }); +} + +export function encodeKey(opts: { action: KeyActionName; code: number }): Uint8Array { + return encode({ + key: { action: KEY_ACTION[opts.action], code: opts.code }, + }); +} + +export function encodeButton(opts: { action: KeyActionName; button: ButtonName }): Uint8Array { + return encode({ + button: { action: KEY_ACTION[opts.action], button: BUTTON_TYPE[opts.button] }, + }); +} + +export function encodeRotate(direction: RotationName): Uint8Array { + return encode({ rotate: { direction: ROTATION[direction] } }); +} + +export function encodeScreenshot(opts?: { + id?: string; + rotation?: RotationName; + scale?: number; +}): Uint8Array { + const screenshot: Record = {}; + if (opts?.id !== undefined) screenshot.id = opts.id; + if (opts?.rotation !== undefined) screenshot.rotation = ROTATION[opts.rotation]; + if (opts?.scale !== undefined) screenshot.scale = opts.scale; + return encode({ screenshot }); +} + +export function encodeWheel(opts: { x: number; y: number; dx: number; dy: number }): Uint8Array { + return encode({ wheel: { x: opts.x, y: opts.y, dx: opts.dx, dy: opts.dy } }); +} diff --git a/packages/tool-server/src/utils/device-info.ts b/packages/tool-server/src/utils/device-info.ts index d66ab400..9bb8d9f5 100644 --- a/packages/tool-server/src/utils/device-info.ts +++ b/packages/tool-server/src/utils/device-info.ts @@ -10,18 +10,37 @@ import type { DeviceInfo, DeviceKind, Platform } from "@argent/registry"; const IOS_UDID_SHAPE = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/; +/** + * Prefix used on device ids that route through `sim-remote` to a remote iOS + * simulator. The raw UUID after the prefix is the same RFC-4122 shape as a + * local iOS UDID — the prefix is the only thing that disambiguates a remote + * sim from a local one. + */ +export const REMOTE_PREFIX = "remote:"; + +/** Strip the `remote:` prefix from a device id, returning the bare UDID. */ +export function stripRemotePrefix(id: string): string { + return id.startsWith(REMOTE_PREFIX) ? id.slice(REMOTE_PREFIX.length) : id; +} + +/** Wrap a bare UDID with the `remote:` prefix used by the ios-remote platform. */ +export function withRemotePrefix(udid: string): string { + return udid.startsWith(REMOTE_PREFIX) ? udid : `${REMOTE_PREFIX}${udid}`; +} + /** Returns the platform a `udid` belongs to based on its shape. */ export function classifyDevice(udid: string): Platform { + if (udid.startsWith(REMOTE_PREFIX)) return "ios-remote"; return IOS_UDID_SHAPE.test(udid) ? "ios" : "android"; } /** * Build a `DeviceInfo` from a raw udid. v1 fills the platform and a default - * kind ('simulator' for iOS, 'emulator' for Android) — platform impls can - * enrich with name/state/sdkLevel via simctl/adb if needed. + * kind ('simulator' for iOS / ios-remote, 'emulator' for Android) — platform + * impls can enrich with name/state/sdkLevel via simctl/adb/sim-remote as needed. */ export function resolveDevice(udid: string): DeviceInfo { const platform = classifyDevice(udid); - const kind: DeviceKind = platform === "ios" ? "simulator" : "emulator"; + const kind: DeviceKind = platform === "android" ? "emulator" : "simulator"; return { id: udid, platform, kind }; } diff --git a/packages/tool-server/src/utils/ios-host.ts b/packages/tool-server/src/utils/ios-host.ts new file mode 100644 index 00000000..7f782bd3 --- /dev/null +++ b/packages/tool-server/src/utils/ios-host.ts @@ -0,0 +1,316 @@ +import { execFile, ChildProcess } from "node:child_process"; +import { EventEmitter } from "node:events"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { promisify } from "node:util"; +import type { DeviceInfo } from "@argent/registry"; +import { + axServiceBinaryPath, + axServiceBinaryPathTcp, + bootstrapDylibPath, + bootstrapDylibPathTcp, +} from "@argent/native-devtools-ios"; +import { SIMCTL_SPAWN_TIMEOUT_MS } from "./simctl-config"; +import { ensureAutomationEnabled, isEntitlementBypassActive } from "./ax-prefs"; +import { + proxyStart as simRemoteProxyStart, + proxyStop as simRemoteProxyStop, + setupAxService as simRemoteSetupAxService, + setupNativeDevtools as simRemoteSetupNativeDevtools, + setupRunningBundleIds as simRemoteRunningBundleIds, +} from "./sim-remote"; + +const execFileAsync = promisify(execFile); + +export type IosEndpoint = + | { transport: "unix"; socketPath: string } + // `port` is optional: omit (or set undefined) to request an ephemeral OS-assigned + // port. The listening side writes the realized port back here, so by the time + // an endpoint flows into the `host.*` functions below it always has `port` set. + | { transport: "tcp"; port?: number }; + +/** + * Strategy that absorbs the local-vs-remote dichotomy out of the iOS + * blueprints (ax-service, native-devtools). Each iOS service factory threads + * its setup/teardown through one of these implementations and reads as a + * linear pipeline instead of an `if (isRemote)` ladder. + */ +export interface IosHost { + readonly kind: "local" | "remote"; + /** When true, the host can only carry TCP traffic (sim-remote tunnel can't bridge unix sockets). */ + readonly requiresTcp: boolean; + + // ── native-devtools steps ── + setupNativeDevtoolsEnv(udid: string, endpoint: IosEndpoint): Promise; + listRunningBundleIds(udid: string): Promise>; + + // ── ax-service steps ── + /** Local probes via `defaults read`; remote assumes the orchestrator handled it. */ + bootstrapAx(udid: string): Promise<{ entitlementBypassActive: boolean }>; + /** + * Local: real `xcrun simctl spawn` process for the ax-service daemon. + * Remote: fire-and-forget orchestrator setup; returns an `EventEmitter` stub + * so the surrounding factory's exit/error wiring and `kill()` on dispose + * still work. + */ + spawnAxDaemon(udid: string, endpoint: IosEndpoint): ChildProcess; + + // ── reverse tunnel (no-op on local) ── + startProxy(udid: string, port: number): Promise; + stopProxy(udid: string, port: number): Promise; +} + +/** Current bootstrap filename; `libInjectionBootstrap.dylib` is legacy (pre-rename) and still stripped when merging env. */ +const ARGENT_BOOTSTRAP_DYLIB_BASENAMES = new Set([ + "libArgentInjectionBootstrap.dylib", + "libInjectionBootstrap.dylib", +]); + +function splitDyldInsertLibraries(value: string): string[] { + return value + .split(":") + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); +} + +/** + * Strips Argent bootstrap dylibs (by basename, including the legacy pre-rename name) + * and entries that don't exist on disk (truncated artifacts from the simctl getenv + * 127-byte bug, stale paths from old installs, etc.). + * Entries starting with '@' (loader-path references) are always preserved. + * Third-party dylibs present on disk (e.g. SimCam) are kept verbatim. + */ +function shouldPreserveDyldInsertLibrariesEntry(entry: string, bootstrapPath: string): boolean { + if (entry === bootstrapPath) { + return false; + } + if (ARGENT_BOOTSTRAP_DYLIB_BASENAMES.has(path.basename(entry))) { + return false; + } + if (entry.startsWith("@")) { + return true; + } + return fs.existsSync(entry); +} + +export function buildDyldInsertLibraries(currentValue: string, bootstrapPath: string): string { + const preserved = splitDyldInsertLibraries(currentValue).filter((entry) => + shouldPreserveDyldInsertLibrariesEntry(entry, bootstrapPath) + ); + return [...preserved, bootstrapPath].join(":"); +} + +async function ensureAccessibilityEnabled(udid: string): Promise { + // iOS 26+ requires AccessibilityEnabled and ApplicationAccessibilityEnabled to be set + // in the simulator's defaults for SwiftUI to populate the accessibility tree. + // Without these flags, all UIAccessibility APIs return nil/0 for SwiftUI views. + const flags = ["AccessibilityEnabled", "ApplicationAccessibilityEnabled"]; + await Promise.all( + flags.map((flag) => + execFileAsync( + "xcrun", + [ + "simctl", + "spawn", + udid, + "defaults", + "write", + "com.apple.Accessibility", + flag, + "-bool", + "true", + ], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ) + ) + ); +} + +async function setupNativeDevtoolsEnvLocal(udid: string, endpoint: IosEndpoint): Promise { + const bootstrapPath = + endpoint.transport === "tcp" ? bootstrapDylibPathTcp() : bootstrapDylibPath(); + + // Read from launchctl inside the simulator (via simctl spawn) instead of + // `simctl getenv`. The latter silently truncates values longer than 127 bytes, + // which corrupts the colon-separated path list and causes stale entries to + // accumulate on every ensureEnv() cycle. + const result = await execFileAsync( + "xcrun", + ["simctl", "spawn", udid, "launchctl", "getenv", "DYLD_INSERT_LIBRARIES"], + { encoding: "utf8", timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ).catch((e) => ({ stdout: (e as NodeJS.ErrnoException & { stdout?: string }).stdout ?? "" })); + + const existing = (result.stdout ?? "").trim(); + const updated = buildDyldInsertLibraries(existing, bootstrapPath); + + if (updated !== existing) { + await execFileAsync( + "xcrun", + ["simctl", "spawn", udid, "launchctl", "setenv", "DYLD_INSERT_LIBRARIES", updated], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ); + } + + if (endpoint.transport === "tcp") { + if (endpoint.port === undefined) { + throw new Error("native-devtools TCP endpoint reached host setup before its port was bound"); + } + await execFileAsync( + "xcrun", + [ + "simctl", + "spawn", + udid, + "launchctl", + "setenv", + "NATIVE_DEVTOOLS_IOS_CDP_PORT", + String(endpoint.port), + ], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ); + } else { + await execFileAsync( + "xcrun", + [ + "simctl", + "spawn", + udid, + "launchctl", + "setenv", + "NATIVE_DEVTOOLS_IOS_CDP_SOCKET", + endpoint.socketPath, + ], + { timeout: SIMCTL_SPAWN_TIMEOUT_MS } + ); + } + + await ensureAccessibilityEnabled(udid); +} + +/** + * Bare basename of the bootstrap dylib the orchestrator should inject. The + * dylib lives on the orchestrator side (it's the TCP variant of the local + * `libArgentInjectionBootstrap.dylib`) and `sim-remote setup native-devtools` + * resolves it by basename against the orchestrator's own dylib directory — + * we never need a local copy. Hardcoding the basename avoids a + * `bootstrapDylibPathTcp()` lookup that would throw on dev machines that + * don't ship the local TCP variant. + */ +const REMOTE_BOOTSTRAP_DYLIB_BASENAME = "libArgentInjectionBootstrap.dylib"; + +async function setupNativeDevtoolsEnvRemote(udid: string, endpoint: IosEndpoint): Promise { + if (endpoint.transport !== "tcp") { + throw new Error("ios-remote native-devtools requires TCP transport"); + } + if (endpoint.port === undefined) { + throw new Error("native-devtools TCP endpoint reached host setup before its port was bound"); + } + await simRemoteSetupNativeDevtools(udid, { + libs: [REMOTE_BOOTSTRAP_DYLIB_BASENAME], + cdpPort: endpoint.port, + }); +} + +async function listRunningUIKitApplicationBundleIds(udid: string): Promise> { + const { stdout } = await execFileAsync("xcrun", ["simctl", "spawn", udid, "launchctl", "list"], { + encoding: "utf8", + }); + + const bundleIds = new Set(); + for (const line of stdout.split("\n")) { + const match = line.match(/UIKitApplication:([^\[]+)/); + if (match) { + bundleIds.add(match[1].trim()); + } + } + return bundleIds; +} + +function spawnAxDaemonLocal(udid: string, endpoint: IosEndpoint): ChildProcess { + const binaryPath = + endpoint.transport === "tcp" ? axServiceBinaryPathTcp() : axServiceBinaryPath(); + + if (endpoint.transport === "tcp" && endpoint.port === undefined) { + throw new Error("ax-service TCP endpoint reached spawn before its port was bound"); + } + const endpointArgs = + endpoint.transport === "tcp" + ? ["--port", String(endpoint.port)] + : ["--socket", endpoint.socketPath]; + + const proc = execFile( + "xcrun", + ["simctl", "spawn", udid, binaryPath, ...endpointArgs, "--timeout", "3600"], + { encoding: "utf8" } + ) as ChildProcess; + + // Defense-in-depth: a missing udid here would crash the process — + // throwing inside an async listener bypasses promise rejection and + // bubbles up as `uncaughtException`, which the tool-server treats as + // fatal. Tag with "?" instead of dereferencing. + const udidTag = typeof udid === "string" && udid.length > 0 ? udid.slice(0, 8) : "?"; + proc.stderr?.on("data", (data: string) => { + process.stderr.write(`[ax-service ${udidTag}] ${data}`); + }); + + return proc; +} + +function spawnAxDaemonRemote(udid: string, endpoint: IosEndpoint): ChildProcess { + if (endpoint.transport !== "tcp") { + throw new Error("ios-remote ax-service requires TCP transport"); + } + if (endpoint.port === undefined) { + throw new Error("ax-service TCP endpoint reached spawn before its port was bound"); + } + // ios-remote: the orchestrator-supplied daemon is started by + // `sim-remote setup ax-service`. There is no local child process to + // shepherd — return a no-op ChildProcess stub so the surrounding factory + // code (exit/error wiring, kill on dispose) still type-checks. + const noop = new EventEmitter() as unknown as ChildProcess; + (noop as unknown as { kill: () => boolean }).kill = () => true; + void simRemoteSetupAxService(udid, { port: endpoint.port, timeoutSecs: 3600 }).catch( + (err: Error) => { + // Defer the emit so listeners attached after this call still see it. + setImmediate(() => noop.emit("error", err)); + } + ); + return noop; +} + +export const localIosHost: IosHost = { + kind: "local", + requiresTcp: false, + setupNativeDevtoolsEnv: setupNativeDevtoolsEnvLocal, + listRunningBundleIds: listRunningUIKitApplicationBundleIds, + async bootstrapAx(udid) { + await ensureAutomationEnabled(udid); + return { entitlementBypassActive: await isEntitlementBypassActive(udid) }; + }, + spawnAxDaemon: spawnAxDaemonLocal, + async startProxy() {}, + async stopProxy() {}, +}; + +export const remoteIosHost: IosHost = { + kind: "remote", + requiresTcp: true, + setupNativeDevtoolsEnv: setupNativeDevtoolsEnvRemote, + async listRunningBundleIds(udid) { + return new Set(await simRemoteRunningBundleIds(udid)); + }, + // sim-remote applies the accessibility defaults at boot via `setup + // accessibility-defaults`, and the entitlement-bypass plist is managed + // there too. Mark the service as non-degraded on the assumption sim-remote + // did the right thing; if not, describe will still surface useful errors. + async bootstrapAx() { + return { entitlementBypassActive: true }; + }, + spawnAxDaemon: spawnAxDaemonRemote, + startProxy: simRemoteProxyStart, + stopProxy: simRemoteProxyStop, +}; + +export function pickIosHost(device: DeviceInfo): IosHost { + return device.platform === "ios-remote" ? remoteIosHost : localIosHost; +} diff --git a/packages/tool-server/src/utils/moq-client.ts b/packages/tool-server/src/utils/moq-client.ts new file mode 100644 index 00000000..14f16c8d --- /dev/null +++ b/packages/tool-server/src/utils/moq-client.ts @@ -0,0 +1,187 @@ +/** + * Pure-JS MoQ client used by the remote simulator-server blueprint. + * + * Talks to the simulator-server's MoQ relay (URL + self-signed cert + * fingerprint surfaced by `sim-remote moq-info`). Subscribes to the + * server-published "simulator" broadcast (catalog/video/screenshot tracks) + * and publishes its own broadcast carrying a "control" track that the + * server subscribes to for protobuf-encoded DataChannelCommand input + * (touch / key / button / rotate / wheel / screenshot). + * + * WebTransport in Node is supplied by `@fails-components/webtransport`, + * which is polyfilled onto `globalThis` lazily on first use. + */ + +import * as Moq from "@moq/net"; +import { encodeScreenshot } from "./datachannel-proto"; +import { moqInfo, type MoqInfo } from "./sim-remote"; + +let polyfillReady: Promise | null = null; + +async function ensurePolyfill(): Promise { + if (polyfillReady) return polyfillReady; + polyfillReady = (async () => { + const g = globalThis as Record; + if (typeof g.WebSocket === "undefined") { + const ws = await import("ws"); + g.WebSocket = ws.default; + } + if (typeof g.WebTransport === "undefined") { + const wt = await import("@fails-components/webtransport"); + g.WebTransport = wt.WebTransport; + // quicheLoaded is a one-shot promise that resolves once the bundled + // libquiche binding finishes loading; awaiting it once is enough. + await wt.quicheLoaded; + } + })(); + return polyfillReady; +} + +/** + * `@moq/net`'s `connect()` races WebTransport + WebSocket via `Promise.any`, + * which wraps the underlying errors in an `AggregateError` whose `.message` + * is the generic "All promises were rejected". Surface the actual failure + * (cert pin mismatch, handshake timeout, missing polyfill, etc.) so the + * agent sees what went wrong instead of a useless aggregate. + */ +function unwrapMoqConnectError(err: unknown, url: string): Error { + if (err instanceof AggregateError) { + const parts = err.errors.map((e) => + e instanceof Error ? `${e.name}: ${e.message}` : String(e) + ); + return new Error(`MoQ connect to ${url} failed: ${parts.join(" | ")}`); + } + if (err instanceof Error) { + return new Error(`MoQ connect to ${url} failed: ${err.message}`); + } + return new Error(`MoQ connect to ${url} failed: ${String(err)}`); +} + +function decodeHexFingerprint(fingerprint: string): Uint8Array { + // Accept "AA:BB:CC..." or "aabbcc..." styles — strip separators, lowercase. + const cleaned = fingerprint.replace(/[^0-9a-fA-F]/g, ""); + if (cleaned.length % 2 !== 0) { + throw new Error(`Invalid MoQ certificate fingerprint (odd hex length): ${fingerprint}`); + } + const out = new Uint8Array(cleaned.length / 2); + for (let i = 0; i < out.length; i++) { + out[i] = parseInt(cleaned.slice(i * 2, i * 2 + 2), 16); + } + return out; +} + +export interface MoqClient { + /** Send one protobuf-encoded DataChannelCommand frame. Awaits the initial control-track subscription on first call. */ + sendControl(payload: Uint8Array): Promise; + /** Request one screenshot and return the decoded PNG/JPEG bytes. Concurrent calls serialise. */ + screenshot(opts?: { scale?: number }): Promise; + /** Tear down the underlying WebTransport session and any in-flight subscriptions. */ + close(): Promise; +} + +/** + * Open a MoQ session to the simulator-server backing the given remote udid. + * Resolves once the WebTransport handshake completes and the local control + * broadcast is published; the control track itself is awaited lazily on the + * first sendControl call. + */ +export async function openMoqClient(udid: string): Promise { + await ensurePolyfill(); + const info: MoqInfo = await moqInfo(udid); + return openMoqClientFromInfo(info); +} + +export async function openMoqClientFromInfo(info: MoqInfo): Promise { + await ensurePolyfill(); + const url = new URL(info.url); + const fingerprint = decodeHexFingerprint(info.fingerprint); + + let established; + try { + established = await Moq.Connection.connect(url, { + webtransport: { + serverCertificateHashes: [{ algorithm: "sha-256", value: fingerprint }], + }, + // WebSocket fallback is pointless against simulator-server (QUIC-only), + // and the default 500ms head-start delay would cost us latency on every + // reconnect. Disable the race. + websocket: { enabled: false }, + }); + } catch (err) { + throw unwrapMoqConnectError(err, info.url); + } + + // --- Server-published "simulator" broadcast (catalog/video/screenshot) --- + const simulator = established.consume(Moq.Path.from("simulator")); + const screenshotTrack = simulator.subscribe("screenshot", 0); + + // --- Client-published "argent" broadcast (control) --- + const controlBroadcast = new Moq.Broadcast(); + established.publish(Moq.Path.from("argent"), controlBroadcast); + + // The server subscribes to our "control" track when it picks up the + // announcement. Cache the requested Track so subsequent sendControl calls + // don't re-await — the first call may have to wait, the rest are O(1). + let controlTrackPromise: Promise | null = null; + const getControlTrack = (): Promise => { + if (controlTrackPromise) return controlTrackPromise; + controlTrackPromise = (async () => { + // Filter out non-"control" track requests in case the server requests + // anything else in the future. + for (;;) { + const req = await controlBroadcast.requested(); + if (!req) { + throw new Error("MoQ control broadcast closed before server subscribed"); + } + if (req.track.name === "control") return req.track; + } + })(); + return controlTrackPromise; + }; + + // Screenshots: serialise concurrent callers so they don't race for the + // next frame on the shared screenshot track (server has no per-request id). + let screenshotChain: Promise = Promise.resolve(); + let screenshotSeq = 0; + + const api: MoqClient = { + async sendControl(payload: Uint8Array): Promise { + const track = await getControlTrack(); + track.writeFrame(payload); + }, + + async screenshot(opts?: { scale?: number }): Promise { + const run = async (): Promise => { + const id = String(++screenshotSeq); + const cmd = encodeScreenshot({ id, scale: opts?.scale }); + const track = await getControlTrack(); + track.writeFrame(cmd); + const frame = await screenshotTrack.readFrame(); + if (!frame) { + throw new Error("MoQ screenshot track closed before frame arrived"); + } + // Server frames the screenshot as `{"data":""}`. + const json = JSON.parse(new TextDecoder().decode(frame)) as { data?: string }; + if (typeof json.data !== "string") { + throw new Error(`MoQ screenshot frame missing 'data' field: ${JSON.stringify(json)}`); + } + return Buffer.from(json.data, "base64"); + }; + const result = screenshotChain.then(run, run); + // Keep the chain advancing regardless of individual failures. + screenshotChain = result.catch(() => undefined); + return result; + }, + + async close(): Promise { + try { + controlBroadcast.close(); + } catch {} + try { + established.close(); + } catch {} + }, + }; + + return api; +} diff --git a/packages/tool-server/src/utils/sim-remote.ts b/packages/tool-server/src/utils/sim-remote.ts new file mode 100644 index 00000000..ab207026 --- /dev/null +++ b/packages/tool-server/src/utils/sim-remote.ts @@ -0,0 +1,215 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; + +const execFileAsync = promisify(execFile); + +/** + * Thin wrapper around the `sim-remote` CLI. + * + * All commands shell out to `sim-remote` and propagate exit-code failures as + * thrown errors with the CLI's stderr appended to the message — so auth and + * orchestrator-side errors reach the agent verbatim instead of being smoothed + * over here. + * + * Each function strips the `remote:` prefix off device ids if present, so + * callers don't have to remember whether the id they're holding has been + * normalised yet. + */ + +import { stripRemotePrefix } from "./device-info"; + +const DEFAULT_TIMEOUT_MS = 30_000; + +interface SimRemoteOptions { + timeoutMs?: number; + stdin?: string; +} + +async function run(args: string[], options?: SimRemoteOptions): Promise<{ stdout: string }> { + try { + const { stdout } = await execFileAsync("sim-remote", args, { + timeout: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS, + maxBuffer: 16 * 1024 * 1024, + encoding: "utf8", + // sim-remote pipes stdin through to pbcopy etc. + input: options?.stdin, + } as Parameters[2]); + return { stdout: typeof stdout === "string" ? stdout : stdout.toString("utf8") }; + } catch (err) { + const e = err as NodeJS.ErrnoException & { stderr?: string; stdout?: string }; + const stderr = (e.stderr ?? "").trim(); + const stdout = (e.stdout ?? "").trim(); + const suffix = stderr || stdout || e.message; + throw new Error(`sim-remote ${args.join(" ")} failed: ${suffix}`); + } +} + +// ── simctl ── + +/** + * Shape of `sim-remote simctl list devices --json`. Mirrors Apple's + * `xcrun simctl list devices --json` output: `{ devices: { : [ ... ] } }`. + */ +export interface SimRemoteDevice { + udid: string; + name: string; + state: string; // "Booted" | "Shutdown" | ... + isAvailable?: boolean; + deviceTypeIdentifier?: string; +} + +export interface SimRemoteListDevicesResult { + devices: Record; +} + +export async function simctlListDevices(): Promise { + const { stdout } = await run(["simctl", "list", "devices", "--json"]); + try { + return JSON.parse(stdout) as SimRemoteListDevicesResult; + } catch (err) { + throw new Error( + `sim-remote simctl list devices --json returned non-JSON output: ${(err as Error).message}` + ); + } +} + +export async function simctlBoot(udid: string): Promise { + await run(["simctl", "boot", stripRemotePrefix(udid)]); +} + +export async function simctlShutdown(udid: string): Promise { + await run(["simctl", "shutdown", stripRemotePrefix(udid)]); +} + +export async function simctlBootstatus(udid: string, opts?: { boot?: boolean }): Promise { + const args = ["simctl", "bootstatus"]; + if (opts?.boot) args.push("-b"); + args.push(stripRemotePrefix(udid)); + // Bootstatus may take a long while on cold boot; give it 5 min. + await run(args, { timeoutMs: 5 * 60_000 }); +} + +export async function simctlLaunch( + udid: string, + bundleId: string, + args: string[] = [] +): Promise { + await run(["simctl", "launch", stripRemotePrefix(udid), bundleId, ...args]); +} + +export async function simctlTerminate(udid: string, bundleId: string): Promise { + await run(["simctl", "terminate", stripRemotePrefix(udid), bundleId]); +} + +export async function simctlInstall(udid: string, localAppPath: string): Promise { + // sim-remote uploads the local .app to the orchestrator over QUIC. + // Large bundles can take a while; give 5 min. + await run(["simctl", "install", stripRemotePrefix(udid), localAppPath], { + timeoutMs: 5 * 60_000, + }); +} + +export async function simctlUninstall(udid: string, bundleId: string): Promise { + await run(["simctl", "uninstall", stripRemotePrefix(udid), bundleId]); +} + +export async function simctlOpenUrl(udid: string, url: string): Promise { + await run(["simctl", "openurl", stripRemotePrefix(udid), url]); +} + +/** Copy the given text into the simulator's pasteboard (sim-remote streams stdin). */ +export async function simctlPbcopy(udid: string, text: string): Promise { + await run(["simctl", "pbcopy", stripRemotePrefix(udid)], { stdin: text }); +} + +export async function simctlPbpaste(udid: string): Promise { + const { stdout } = await run(["simctl", "pbpaste", stripRemotePrefix(udid)]); + return stdout; +} + +// ── setup ── + +export async function setupAccessibilityDefaults(udid: string): Promise { + await run(["setup", "accessibility-defaults", stripRemotePrefix(udid)]); +} + +export async function setupNativeDevtools( + udid: string, + opts: { libs: string[]; cdpPort: number } +): Promise { + const args = ["setup", "native-devtools"]; + for (const lib of opts.libs) args.push("--lib", lib); + args.push("--cdp-port", String(opts.cdpPort), stripRemotePrefix(udid)); + await run(args); +} + +/** + * Returns the bundle ids of UIKitApplications currently running on the + * remote simulator (orchestrator-side `setup running-bundle-ids`). + */ +export async function setupRunningBundleIds(udid: string): Promise { + const { stdout } = await run(["setup", "running-bundle-ids", stripRemotePrefix(udid)]); + return stdout + .split("\n") + .map((s) => s.trim()) + .filter((s) => s.length > 0); +} + +export async function setupAxService( + udid: string, + opts: { port: number; timeoutSecs?: number } +): Promise { + await run([ + "setup", + "ax-service", + "--port", + String(opts.port), + "--timeout-secs", + String(opts.timeoutSecs ?? 3600), + stripRemotePrefix(udid), + ]); +} + +// ── proxy ── + +/** + * Start a TCP tunnel: incoming connections on the host's `localhost:` + * are forwarded by the daemon to the same port inside the remote simulator. + * + * Idempotent: re-running with the same (udid, port) tolerates "already + * started" errors so blueprints don't have to track tunnel ownership across + * service restarts. + */ +export async function proxyStart(udid: string, port: number): Promise { + try { + await run(["proxy", "start", stripRemotePrefix(udid), String(port)]); + } catch (err) { + const message = (err as Error).message ?? ""; + if (/already/i.test(message)) return; + throw err; + } +} + +export async function proxyStop(udid: string, port: number): Promise { + try { + await run(["proxy", "stop", stripRemotePrefix(udid), String(port)]); + } catch { + // best-effort cleanup + } +} + +// ── moq ── + +export interface MoqInfo { + url: string; + fingerprint: string; +} + +export async function moqInfo(udid: string): Promise { + const { stdout } = await run(["moq-info", stripRemotePrefix(udid)]); + try { + return JSON.parse(stdout) as MoqInfo; + } catch (err) { + throw new Error(`sim-remote moq-info returned non-JSON output: ${(err as Error).message}`); + } +} diff --git a/packages/tool-server/src/utils/simctl-backend.ts b/packages/tool-server/src/utils/simctl-backend.ts new file mode 100644 index 00000000..3972faad --- /dev/null +++ b/packages/tool-server/src/utils/simctl-backend.ts @@ -0,0 +1,29 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { simctlLaunch, simctlTerminate } from "./sim-remote"; + +const execFileAsync = promisify(execFile); + +/** + * Strategy for the simctl verbs that a tool handler shells out to. Lets a + * single iOS handler serve both local sims (`xcrun simctl`) and remote sims + * (`sim-remote simctl`) without an `isRemote` branch inside the handler body. + */ +export interface SimctlBackend { + launch(udid: string, bundleId: string): Promise; + terminate(udid: string, bundleId: string): Promise; +} + +export const localSimctl: SimctlBackend = { + async launch(udid, bundleId) { + await execFileAsync("xcrun", ["simctl", "launch", udid, bundleId]); + }, + async terminate(udid, bundleId) { + await execFileAsync("xcrun", ["simctl", "terminate", udid, bundleId]); + }, +}; + +export const remoteSimctl: SimctlBackend = { + launch: simctlLaunch, + terminate: simctlTerminate, +}; diff --git a/packages/tool-server/src/utils/simulator-client.ts b/packages/tool-server/src/utils/simulator-client.ts index d8c2b5d7..a722657b 100644 --- a/packages/tool-server/src/utils/simulator-client.ts +++ b/packages/tool-server/src/utils/simulator-client.ts @@ -1,9 +1,51 @@ import WebSocket from "ws"; import type { SimulatorServerApi } from "../blueprints/simulator-server"; import { toSimulatorNetworkError } from "./format-error"; +import { + encodeButton, + encodeKey, + encodeRotate, + encodeTouch, + type ButtonName, + type KeyActionName, + type RotationName, + type TouchActionName, +} from "./datachannel-proto"; +import type { MoqClient } from "./moq-client"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import * as os from "node:os"; +import { randomUUID } from "node:crypto"; +import { pathToFileURL } from "node:url"; const DEFAULT_SCREENSHOT_SCALE = 0.3; +/** + * Transport-level interface every `SimulatorServerApi` produces. Local sims + * back this with the WebSocket+HTTP client; remote sims back it with a MoQ + * client. Keeping the high-level shape (touch/button/rotate/screenshot) + * here means every tool call site stays transport-agnostic. + */ +export interface SimulatorServerTransport { + touch(opts: { + type: TouchActionName; + x: number; + y: number; + secondX?: number; + secondY?: number; + }): void; + button(opts: { direction: KeyActionName; button: ButtonName }): void; + rotate(direction: RotationName): void; + /** Multi-character text paste (host pasteboard → simulator pasteboard + Cmd+V on remote). */ + paste(text: string): Promise | void; + pressKey(direction: KeyActionName, keyCode: number): void; + screenshot(opts?: { + rotation?: RotationName; + scale?: number; + signal?: AbortSignal; + }): Promise<{ url: string; path: string }>; +} + const connections = new Map(); let cmdId = 0; @@ -25,10 +67,18 @@ function getOrCreateWs(api: SimulatorServerApi): WebSocket { } /** - * Send a command to the simulator-server over WebSocket. - * Reuses a single connection per apiUrl. + * Send a JSON command to the simulator-server. + * + * On local sims this goes over the WebSocket; on remote sims (when + * `api.transport` is set) it is routed through the MoQ-backed transport. + * Call sites stay transport-agnostic — they always speak the WebSocket + * command shape (`{cmd: "touch", ...}`). */ -export function sendCommand(api: SimulatorServerApi, cmd: object): void { +export function sendCommand(api: SimulatorServerApi, cmd: Record): void { + if (api.transport) { + routeViaTransport(api.transport, cmd); + return; + } const ws = getOrCreateWs(api); const payload = JSON.stringify({ id: String(++cmdId), ...cmd }); if (ws.readyState === WebSocket.OPEN) { @@ -86,6 +136,10 @@ export function getScreenshotScale(): number { /** * Take a screenshot via the simulator-server HTTP API. + * + * If the api has a `transport` field set (e.g. MoQ for ios-remote), the + * transport's screenshot method is used instead — the response shape + * (`{ url, path }`) is preserved either way. */ export async function httpScreenshot( api: SimulatorServerApi, @@ -93,6 +147,13 @@ export async function httpScreenshot( signal?: AbortSignal, scale?: number ): Promise<{ url: string; path: string }> { + if (api.transport) { + return api.transport.screenshot({ + rotation: rotation as RotationName | undefined, + scale, + signal, + }); + } const resolvedScale = scale ?? getScreenshotScale(); const body: Record = {}; if (rotation) body.rotation = rotation; @@ -119,3 +180,99 @@ export async function httpScreenshot( } return { url: resBody.url, path: resBody.path }; } + +function routeViaTransport( + transport: SimulatorServerTransport, + cmd: Record +): void { + switch (cmd.cmd) { + case "touch": { + // Local WebSocket protocol uses snake_case second_x/second_y (set to + // null when absent); the proto-encoder takes optional secondX/secondY. + const sx = (cmd.second_x ?? cmd.secondX) as number | null | undefined; + const sy = (cmd.second_y ?? cmd.secondY) as number | null | undefined; + transport.touch({ + type: cmd.type as TouchActionName, + x: cmd.x as number, + y: cmd.y as number, + secondX: sx == null ? undefined : sx, + secondY: sy == null ? undefined : sy, + }); + return; + } + case "button": + transport.button({ + direction: cmd.direction as KeyActionName, + button: cmd.button as ButtonName, + }); + return; + case "rotate": + transport.rotate(cmd.direction as RotationName); + return; + case "paste": { + // paste() may be async on remote (pbcopy + Cmd+V); fire and forget + // here to preserve sendCommand's sync shape. Errors land in the host + // process's unhandledRejection logger — same as a websocket send fail. + void Promise.resolve(transport.paste(cmd.text as string)); + return; + } + default: + throw new Error(`MoQ transport does not implement sendCommand cmd '${String(cmd.cmd)}'`); + } +} + +// ── MoQ transport adapter ───────────────────────────────────────────────── + +/** + * Build a `SimulatorServerTransport` that routes touch/button/rotate/key/ + * screenshot operations over an `@moq/net`-backed `MoqClient`. Screenshots + * are written into argent's temp dir to match the local HTTP path's + * `{ url, path }` contract. + */ +export function createMoqTransport( + moq: MoqClient, + options: { pasteText: (text: string) => Promise } +): SimulatorServerTransport { + const screenshotDir = path.join(os.tmpdir(), "argent-remote-screenshots"); + + const writeScreenshotToDisk = async (bytes: Buffer): Promise<{ url: string; path: string }> => { + await fs.mkdir(screenshotDir, { recursive: true }); + const file = path.join(screenshotDir, `${randomUUID()}.png`); + await fs.writeFile(file, bytes); + return { url: pathToFileURL(file).toString(), path: file }; + }; + + return { + touch(opts) { + void moq.sendControl( + encodeTouch({ + action: opts.type, + x: opts.x, + y: opts.y, + secondX: opts.secondX, + secondY: opts.secondY, + }) + ); + }, + button(opts) { + void moq.sendControl(encodeButton({ action: opts.direction, button: opts.button })); + }, + rotate(direction) { + void moq.sendControl(encodeRotate(direction)); + }, + async paste(text) { + // sim-remote pbcopy + Cmd+V on the remote sim. The cmd+v sequence is + // emitted on the host via the option's pasteText callback so the + // transport stays platform-agnostic. + await options.pasteText(text); + }, + pressKey(direction, keyCode) { + void moq.sendControl(encodeKey({ action: direction, code: keyCode })); + }, + async screenshot(opts) { + const scale = opts?.scale ?? getScreenshotScale(); + const bytes = await moq.screenshot({ scale }); + return writeScreenshotToDisk(bytes); + }, + }; +} diff --git a/packages/tool-server/tsconfig.json b/packages/tool-server/tsconfig.json index 3b8ac147..733d152d 100644 --- a/packages/tool-server/tsconfig.json +++ b/packages/tool-server/tsconfig.json @@ -4,7 +4,8 @@ "composite": true, "declaration": true, "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + "customConditions": ["node"] }, "references": [ { "path": "../registry" },