diff --git a/package-lock.json b/package-lock.json index e85e0fe..6485afa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,15 +33,11 @@ }, "node_modules/@adobe/css-tools": { "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true, "license": "MIT" }, "node_modules/@agent-relay/bridge": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/bridge/-/bridge-2.3.14.tgz", - "integrity": "sha512-z2LePaSh+IOjlxjoPSLNwknosi+/LdwZEuVothFEjt4MGAUrG03hSi94rb5bVlm6ZpC2Cz2pKxg9oxddPCH47w==", "dependencies": { "@agent-relay/config": "2.3.14", "@agent-relay/policy": "2.3.14", @@ -53,8 +49,6 @@ }, "node_modules/@agent-relay/bridge/node_modules/@agent-relay/config": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-2.3.14.tgz", - "integrity": "sha512-At9l8U/6AJrD8bIwK7SwycSkxk279Z0e3gGUGSKHBIAUMBL5gzH7+NSqiL8YtFCywQmgF7BBDkHbTIVwPPmooQ==", "dependencies": { "@agent-relay/protocol": "2.3.14", "zod": "^3.23.8", @@ -63,8 +57,6 @@ }, "node_modules/@agent-relay/bridge/node_modules/@agent-relay/utils": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/utils/-/utils-2.3.14.tgz", - "integrity": "sha512-mQHwOUo4CkkXASdTHGgw5yiwGhhrOaMWnPO7v4pL66ryZnMSIIuiWLPJfnOquQ4zkDZkr3pHIyrjzyVm03Rthw==", "dependencies": { "@agent-relay/config": "2.3.14", "@agent-relay/protocol": "2.3.14", @@ -73,8 +65,6 @@ }, "node_modules/@agent-relay/config": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-3.2.5.tgz", - "integrity": "sha512-kLnyh1cBvIoUDxe+NBeXVb3P6sYugbRQuyLBrdCOwpP05WnTx42Z0MDO/vijhoYnIWqaBh3TZWRymuDPoqPmPw==", "dependencies": { "zod": "^3.23.8", "zod-to-json-schema": "^3.23.1" @@ -82,8 +72,6 @@ }, "node_modules/@agent-relay/continuity": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/continuity/-/continuity-2.3.14.tgz", - "integrity": "sha512-TXNEyJ9kidUOR/gcpZdzToBijmW0zZj/jgLkg4ez3N3D9UYf+HxxQRnnButU6i7IWvMkUr6gLPVm5rups18i3g==", "dependencies": { "@agent-relay/memory": "2.3.14" } @@ -98,8 +86,6 @@ }, "node_modules/@agent-relay/hooks": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/hooks/-/hooks-2.3.14.tgz", - "integrity": "sha512-Cenn8qJyuNjEcfx0P0ESPrCnDhZVe+UuvkQqqKKgJmSANauTVC9GmyhUbKA5nPp5lL8C2+R7CUGpBKejjcrD5A==", "dependencies": { "@agent-relay/config": "2.3.14", "@agent-relay/protocol": "2.3.14", @@ -108,8 +94,6 @@ }, "node_modules/@agent-relay/hooks/node_modules/@agent-relay/config": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-2.3.14.tgz", - "integrity": "sha512-At9l8U/6AJrD8bIwK7SwycSkxk279Z0e3gGUGSKHBIAUMBL5gzH7+NSqiL8YtFCywQmgF7BBDkHbTIVwPPmooQ==", "dependencies": { "@agent-relay/protocol": "2.3.14", "zod": "^3.23.8", @@ -118,32 +102,24 @@ }, "node_modules/@agent-relay/hooks/node_modules/@agent-relay/trajectory": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/trajectory/-/trajectory-2.3.14.tgz", - "integrity": "sha512-S5ABnkR5WEuiJKS5GyH2dtDW6N5c1WmetNOPG+I96+RGT4CszWSOyeUyHmPaqNMp1WGNnA9XBndhkdjMJIQOaA==", "dependencies": { "@agent-relay/config": "2.3.14" } }, "node_modules/@agent-relay/memory": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/memory/-/memory-2.3.14.tgz", - "integrity": "sha512-E43cGmgWevt4AlVf/MSu9+ljxQvSF0C0oGgKqyXzgcqF2D60gJkcX8PEUijflyXD98GAgGSUudCvV4UAxPjiXQ==", "dependencies": { "@agent-relay/hooks": "2.3.14" } }, "node_modules/@agent-relay/policy": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/policy/-/policy-2.3.14.tgz", - "integrity": "sha512-lv7SsC9lFCtX3m28AhxJnvtbWLymXJ6H+4K6q1V3BJ85GbzTKVeedFJiRhHU+FZsg3LLRcSsXgn5Es5WCkA0NQ==", "dependencies": { "@agent-relay/config": "2.3.14" } }, "node_modules/@agent-relay/policy/node_modules/@agent-relay/config": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-2.3.14.tgz", - "integrity": "sha512-At9l8U/6AJrD8bIwK7SwycSkxk279Z0e3gGUGSKHBIAUMBL5gzH7+NSqiL8YtFCywQmgF7BBDkHbTIVwPPmooQ==", "dependencies": { "@agent-relay/protocol": "2.3.14", "zod": "^3.23.8", @@ -152,21 +128,15 @@ }, "node_modules/@agent-relay/protocol": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/protocol/-/protocol-2.3.14.tgz", - "integrity": "sha512-7Y31vZN3axBnMIEW+3Oj1zIgqqtkW83BbNYjzjS+m3hX7zIl8sVfhaEFC09eWjhkknDhCzU+95Y+WyZ730iOTw==", "optionalDependencies": { "@msgpack/msgpack": "^3.0.0" } }, "node_modules/@agent-relay/resiliency": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/resiliency/-/resiliency-2.3.14.tgz", - "integrity": "sha512-MZksBQyNMG5whV1z9FvJYzSOY4UU2kctCIRayfigAL5L2MtAwBu2fJSTDUVoFZM6X8MwdnjDqQ/govrmupXYsA==" + "version": "2.3.14" }, "node_modules/@agent-relay/sdk": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@agent-relay/sdk/-/sdk-3.2.5.tgz", - "integrity": "sha512-wlNfS/DSfGX6HHk0dNRIZNMJsgx9f0CciLeCstgprSCBuB8qfhaGkYIvX8gHxRCSgNyCr5fYixmKgdx2f+sVsw==", "dependencies": { "@agent-relay/config": "3.2.5", "@relaycast/sdk": "^0.4.0", @@ -205,8 +175,6 @@ }, "node_modules/@agent-relay/storage": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/storage/-/storage-2.3.14.tgz", - "integrity": "sha512-ZjMv+dvt/+rY2Aga5vTVhmvDic6tc/Xs7jKn3uK8BPXnJIChRkNmaQWtvu0z+QheWbNfzyLHpb1aRebVTX6WXQ==", "dependencies": { "@agent-relay/protocol": "2.3.14" }, @@ -221,24 +189,18 @@ }, "node_modules/@agent-relay/trajectory": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@agent-relay/trajectory/-/trajectory-3.2.5.tgz", - "integrity": "sha512-XeAB9l9ZbxtKtdotLUP8Vvj8ZyVZcVwK7r6RcWdqDCu6sOZ7OTUYsedPvKGubzmfs2Nlu4u17TJNBE3zG4ek3A==", "dependencies": { "@agent-relay/config": "3.2.5" } }, "node_modules/@agent-relay/user-directory": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/user-directory/-/user-directory-2.3.14.tgz", - "integrity": "sha512-392Yl88YqHTdI71/NsdGKZ3Z54ou37a7QuuSe8A+13niNTjU14otTT2TAfiyux6wk7jyifAMOT5ijIaF+N/2pQ==", "dependencies": { "@agent-relay/resiliency": "2.3.14" } }, "node_modules/@agent-relay/utils": { "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@agent-relay/utils/-/utils-3.2.5.tgz", - "integrity": "sha512-yU63bmvOC22MrIlNVKymsVOHQpxQjcnDxrghFVR/6j3DMnMMkSkqXJD+MwkRufhfz5l0bj2CrBCMdVp1x5pF7g==", "dependencies": { "@agent-relay/config": "3.2.5", "compare-versions": "^6.1.1" @@ -246,8 +208,6 @@ }, "node_modules/@agent-relay/wrapper": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/wrapper/-/wrapper-2.3.14.tgz", - "integrity": "sha512-kCCLoLKjSTiR+xOQoJQjfw5cPixBSaUTj6bXQv9RDeGMXR8oEVHTefpC6fUH5hxedzqBe4ZCLbKGXZudEWp4jQ==", "license": "Apache-2.0", "dependencies": { "@agent-relay/config": "2.3.14", @@ -259,8 +219,6 @@ }, "node_modules/@agent-relay/wrapper/node_modules/@agent-relay/config": { "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-2.3.14.tgz", - "integrity": "sha512-At9l8U/6AJrD8bIwK7SwycSkxk279Z0e3gGUGSKHBIAUMBL5gzH7+NSqiL8YtFCywQmgF7BBDkHbTIVwPPmooQ==", "dependencies": { "@agent-relay/protocol": "2.3.14", "zod": "^3.23.8", @@ -269,8 +227,6 @@ }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, "license": "MIT", "engines": { @@ -282,8 +238,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -296,8 +250,6 @@ }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", "dependencies": { @@ -310,8 +262,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -325,8 +275,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -335,8 +283,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -345,8 +291,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { @@ -361,8 +305,6 @@ }, "node_modules/@babel/runtime": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -370,8 +312,6 @@ }, "node_modules/@babel/types": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -384,8 +324,6 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -394,8 +332,6 @@ }, "node_modules/@clack/core": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", - "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -405,8 +341,6 @@ }, "node_modules/@clack/prompts": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", - "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", "bundleDependencies": [ "is-unicode-supported" ], @@ -414,14 +348,13 @@ "license": "MIT", "dependencies": { "@clack/core": "^0.3.3", - "is-unicode-supported": "*", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -433,8 +366,6 @@ }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "dev": true, "funding": [ { @@ -453,8 +384,6 @@ }, "node_modules/@csstools/css-calc": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, "funding": [ { @@ -477,8 +406,6 @@ }, "node_modules/@csstools/css-color-parser": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "dev": true, "funding": [ { @@ -505,8 +432,6 @@ }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, "funding": [ { @@ -528,8 +453,6 @@ }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, "funding": [ { @@ -546,1442 +469,291 @@ "node": ">=18" } }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "aix" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], + "node_modules/@isaacs/cliui": { + "version": "8.0.2", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", + "node_modules/@msgpack/msgpack": { + "version": "3.1.3", + "license": "ISC", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">= 18" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@nangohq/frontend": { + "version": "0.69.38", + "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", + "dependencies": { + "@nangohq/types": "0.69.38" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@nangohq/types": { + "version": "0.69.38", + "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", + "dependencies": { + "axios": "1.13.5", + "json-schema": "0.4.0", + "type-fest": "4.41.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "node_modules/@next/env": { + "version": "14.2.35", + "license": "MIT" }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.33", "cpu": [ - "mips64el" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], + "node_modules/@noble/hashes": { + "version": "1.8.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@noble/hashes": "^1.1.5" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=14" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@relaycast/react": { + "version": "0.5.1", + "license": "Apache-2.0", + "dependencies": { + "@relaycast/sdk": "0.5.1", + "@relaycast/types": "0.5.1", + "prism-react-renderer": "^2.4.1", + "react-markdown": "^10.1.0", + "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "node_modules/@relaycast/react/node_modules/@relaycast/sdk": { + "version": "0.5.1", + "dependencies": { + "@relaycast/types": "0.5.1", + "zod": "^4.3.6" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@relaycast/react/node_modules/zod": { + "version": "4.3.6", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", + "node_modules/@relaycast/sdk": { + "version": "0.4.2", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@relaycast/types": "0.4.2", + "zod": "^4.3.6" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", + "node_modules/@relaycast/sdk/node_modules/@relaycast/types": { + "version": "0.4.2", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "zod": "^4.3.6" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/@relaycast/sdk/node_modules/zod": { + "version": "4.3.6", "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@msgpack/msgpack": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", - "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nangohq/frontend": { - "version": "0.69.38", - "resolved": "https://registry.npmjs.org/@nangohq/frontend/-/frontend-0.69.38.tgz", - "integrity": "sha512-ijSmxtmLitHqkuGsKUH9u5LaXtjKU3wmqglmNHmYOb68uBGvyZVS+KunFXo/bA6D6LSdJJC8JDhlAWsak7siwA==", - "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", - "dependencies": { - "@nangohq/types": "0.69.38" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@nangohq/types": { - "version": "0.69.38", - "resolved": "https://registry.npmjs.org/@nangohq/types/-/types-0.69.38.tgz", - "integrity": "sha512-YHTk4+55PtguG094qOZaRdJ1qXB8wTmffpq6vdQYhGzZJUk9TPRqHA0P5i2FM/ZuOczX6ciHOmUWmqykxHaRog==", - "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", + "node_modules/@relaycast/types": { + "version": "0.5.1", "dependencies": { - "axios": "1.13.5", - "json-schema": "0.4.0", - "type-fest": "4.41.0" - } - }, - "node_modules/@next/env": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", - "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", - "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", - "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", - "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", - "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", - "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", - "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "zod": "^4.3.6" } }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", - "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", - "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", - "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@relaycast/react": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@relaycast/react/-/react-0.5.1.tgz", - "integrity": "sha512-k9sAxC3S+XTEXPH7yxxlL2ckvGz5NH54ym/MPqtOE+1HNLLLf6ZrSBDOQzzcdNrqcntBlCWvhFYmfbjmG4y1Aw==", - "license": "Apache-2.0", - "dependencies": { - "@relaycast/sdk": "0.5.1", - "@relaycast/types": "0.5.1", - "prism-react-renderer": "^2.4.1", - "react-markdown": "^10.1.0", - "remark-breaks": "^4.0.0", - "remark-gfm": "^4.0.1" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@relaycast/react/node_modules/@relaycast/sdk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-0.5.1.tgz", - "integrity": "sha512-BGmk4dfig1SBOK6HDGGS7/t4Kek9MC8mCyJLVpwQ8jIg6552j6iXMQGiirj95H78RqghPfAx3cjPNsTJ+uCcrw==", - "dependencies": { - "@relaycast/types": "0.5.1", - "zod": "^4.3.6" - } - }, - "node_modules/@relaycast/react/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@relaycast/sdk": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-0.4.2.tgz", - "integrity": "sha512-PwaINyigfmnQV4cmUTRMMSXyRbuCGIiH6ODeA3mKhO4TmdzIAFA6GU/Z6NcJdVbq6ES7gdW1hJ3Cn9AGKw2cgw==", - "dependencies": { - "@relaycast/types": "0.4.2", - "zod": "^4.3.6" - } - }, - "node_modules/@relaycast/sdk/node_modules/@relaycast/types": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@relaycast/types/-/types-0.4.2.tgz", - "integrity": "sha512-y+kmFBmlRzGdp/5i5ioTsY2imB3Z7XI05YxdwabHAgER01TK/SGfZLtp6gFuM342mVPcFg+mNRfWYR0SNuRkTw==", - "dependencies": { - "zod": "^4.3.6" - } - }, - "node_modules/@relaycast/sdk/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@relaycast/types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@relaycast/types/-/types-0.5.1.tgz", - "integrity": "sha512-Hj8QZhJ2x6mq+KDAm1c4Yvnw7ROlza+D1KIx/GlKVTv3lAHugEE47AUpiFRteat2gClF92oxq9It0Oou5TUl8g==", - "dependencies": { - "zod": "^4.3.6" - } - }, - "node_modules/@relaycast/types/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "license": "MIT" - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", - "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.31.1", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.1" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", - "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-arm64": "4.2.1", - "@tailwindcss/oxide-darwin-x64": "4.2.1", - "@tailwindcss/oxide-freebsd-x64": "4.2.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", - "@tailwindcss/oxide-linux-x64-musl": "4.2.1", - "@tailwindcss/oxide-wasm32-wasi": "4.2.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", - "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", - "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", - "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", - "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", - "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", - "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", - "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", - "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" + "node_modules/@relaycast/types/node_modules/zod": { + "version": "4.3.6", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", - "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">= 20" + "darwin" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "node_modules/@tailwindcss/node": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", - "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "node_modules/@tailwindcss/oxide": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", - "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", - "cpu": [ - "arm64" - ], "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", - "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": ">= 20" @@ -1989,8 +761,6 @@ }, "node_modules/@tailwindcss/postcss": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", - "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2003,8 +773,6 @@ }, "node_modules/@testing-library/dom": { "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", - "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2023,8 +791,6 @@ }, "node_modules/@testing-library/dom/node_modules/aria-query": { "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2033,15 +799,11 @@ }, "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, "license": "MIT" }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", "dependencies": { @@ -2060,8 +822,6 @@ }, "node_modules/@testing-library/react": { "version": "14.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", - "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2079,15 +839,11 @@ }, "node_modules/@types/aria-query": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, "license": "MIT" }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -2097,8 +853,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": { @@ -2108,8 +862,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { @@ -2118,15 +870,11 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/debug": { "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -2134,21 +882,15 @@ }, "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.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { "@types/estree": "*" @@ -2156,8 +898,6 @@ }, "node_modules/@types/express": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { @@ -2168,8 +908,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2181,8 +919,6 @@ }, "node_modules/@types/hast": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -2190,15 +926,11 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.17", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -2206,8 +938,6 @@ }, "node_modules/@types/mdast": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -2215,21 +945,15 @@ }, "node_modules/@types/methods": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/node": { "version": "22.19.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", - "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2237,34 +961,24 @@ }, "node_modules/@types/prismjs": { "version": "1.26.6", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", - "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "license": "MIT" }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2273,8 +987,6 @@ }, "node_modules/@types/react-dom": { "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2283,8 +995,6 @@ }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.13", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", - "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", "dev": true, "license": "MIT", "dependencies": { @@ -2293,8 +1003,6 @@ }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2303,8 +1011,6 @@ }, "node_modules/@types/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2314,8 +1020,6 @@ }, "node_modules/@types/superagent": { "version": "8.1.9", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", - "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2327,8 +1031,6 @@ }, "node_modules/@types/supertest": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", - "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "license": "MIT", "dependencies": { @@ -2338,14 +1040,10 @@ }, "node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { @@ -2354,14 +1052,10 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2394,8 +1088,6 @@ }, "node_modules/@vitest/expect": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { @@ -2411,8 +1103,6 @@ }, "node_modules/@vitest/mocker": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2438,8 +1128,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -2451,8 +1139,6 @@ }, "node_modules/@vitest/runner": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2466,8 +1152,6 @@ }, "node_modules/@vitest/snapshot": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2481,8 +1165,6 @@ }, "node_modules/@vitest/spy": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { @@ -2494,8 +1176,6 @@ }, "node_modules/@vitest/utils": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { @@ -2509,20 +1189,14 @@ }, "node_modules/@xterm/addon-fit": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", - "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", "license": "MIT" }, "node_modules/@xterm/addon-search": { "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz", - "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==", "license": "MIT" }, "node_modules/@xterm/xterm": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", - "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", "license": "MIT", "workspaces": [ "addons/*" @@ -2530,8 +1204,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -2543,8 +1215,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { @@ -2553,8 +1223,6 @@ }, "node_modules/agent-trajectories": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/agent-trajectories/-/agent-trajectories-0.3.1.tgz", - "integrity": "sha512-3Futf5LbPopgtrELoYQSMkcq3d7WpajTYbOwWcVcSf3aN2Kj5Tr/Xx/pJ7F7uAGHO5BJV4FzKJGJw3i38j1Wag==", "dev": true, "license": "MIT", "dependencies": { @@ -2571,8 +1239,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2581,8 +1247,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2597,8 +1261,6 @@ }, "node_modules/aria-query": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2607,8 +1269,6 @@ }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { @@ -2624,15 +1284,11 @@ }, "node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "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": { @@ -2641,8 +1297,6 @@ }, "node_modules/ast-v8-to-istanbul": { "version": "0.3.12", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", - "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2653,21 +1307,15 @@ }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/autoprefixer": { "version": "10.4.27", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "dev": true, "funding": [ { @@ -2703,8 +1351,6 @@ }, "node_modules/available-typed-arrays": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2719,8 +1365,6 @@ }, "node_modules/axios": { "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", @@ -2730,8 +1374,6 @@ }, "node_modules/bail": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", "funding": { "type": "github", @@ -2740,8 +1382,6 @@ }, "node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { @@ -2750,8 +1390,6 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2763,8 +1401,6 @@ }, "node_modules/body-parser": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -2787,8 +1423,6 @@ }, "node_modules/brace-expansion": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2800,8 +1434,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2812,8 +1444,6 @@ }, "node_modules/browserslist": { "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2846,8 +1476,6 @@ }, "node_modules/busboy": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { "streamsearch": "^1.1.0" }, @@ -2857,8 +1485,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2866,8 +1492,6 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -2876,8 +1500,6 @@ }, "node_modules/call-bind": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "license": "MIT", "dependencies": { @@ -2895,8 +1517,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2908,8 +1528,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2924,8 +1542,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001775", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", - "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "funding": [ { "type": "opencollective", @@ -2944,8 +1560,6 @@ }, "node_modules/ccount": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", "funding": { "type": "github", @@ -2954,8 +1568,6 @@ }, "node_modules/chai": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", "dependencies": { @@ -2971,8 +1583,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -2988,8 +1598,6 @@ }, "node_modules/character-entities": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { "type": "github", @@ -2998,8 +1606,6 @@ }, "node_modules/character-entities-html4": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", "funding": { "type": "github", @@ -3008,8 +1614,6 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { "type": "github", @@ -3018,8 +1622,6 @@ }, "node_modules/character-reference-invalid": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", "funding": { "type": "github", @@ -3028,8 +1630,6 @@ }, "node_modules/check-error": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", "engines": { @@ -3038,14 +1638,10 @@ }, "node_modules/client-only": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3059,15 +1655,11 @@ }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3081,8 +1673,6 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3094,8 +1684,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3112,8 +1700,6 @@ }, "node_modules/clsx": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" @@ -3121,8 +1707,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3134,15 +1718,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -3153,8 +1733,6 @@ }, "node_modules/comma-separated-tokens": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { "type": "github", @@ -3163,8 +1741,6 @@ }, "node_modules/commander": { "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { @@ -3173,14 +1749,10 @@ }, "node_modules/compare-versions": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", "license": "MIT" }, "node_modules/component-emitter": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -3189,8 +1761,6 @@ }, "node_modules/concurrently": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, "license": "MIT", "dependencies": { @@ -3217,8 +1787,6 @@ }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3233,8 +1801,6 @@ }, "node_modules/content-disposition": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", "engines": { "node": ">=18" @@ -3246,8 +1812,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3255,8 +1819,6 @@ }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3264,8 +1826,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -3273,15 +1833,11 @@ }, "node_modules/cookiejar": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -3295,15 +1851,11 @@ }, "node_modules/css.escape": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true, "license": "MIT" }, "node_modules/cssstyle": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", "dependencies": { @@ -3316,21 +1868,15 @@ }, "node_modules/cssstyle/node_modules/rrweb-cssom": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/data-urls": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { @@ -3343,8 +1889,6 @@ }, "node_modules/date-fns": { "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3360,8 +1904,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3377,15 +1919,11 @@ }, "node_modules/decimal.js": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, "license": "MIT" }, "node_modules/decode-named-character-reference": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -3397,8 +1935,6 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -3407,8 +1943,6 @@ }, "node_modules/deep-equal": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "license": "MIT", "dependencies": { @@ -3440,8 +1974,6 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "dependencies": { @@ -3458,8 +1990,6 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -3476,8 +2006,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3485,8 +2013,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3494,8 +2020,6 @@ }, "node_modules/dequal": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", "engines": { "node": ">=6" @@ -3503,8 +2027,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": { @@ -3513,8 +2035,6 @@ }, "node_modules/devlop": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -3526,8 +2046,6 @@ }, "node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "license": "ISC", "dependencies": { @@ -3537,15 +2055,11 @@ }, "node_modules/dom-accessibility-api": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true, "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3558,35 +2072,25 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3594,8 +2098,6 @@ }, "node_modules/enhanced-resolve": { "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3608,8 +2110,6 @@ }, "node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3621,8 +2121,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3630,8 +2128,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3639,8 +2135,6 @@ }, "node_modules/es-get-iterator": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "dev": true, "license": "MIT", "dependencies": { @@ -3660,15 +2154,11 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3679,8 +2169,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3694,8 +2182,6 @@ }, "node_modules/esbuild": { "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3736,8 +2222,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -3746,14 +2230,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { "node": ">=12" @@ -3764,8 +2244,6 @@ }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "license": "MIT", "funding": { "type": "opencollective", @@ -3774,8 +2252,6 @@ }, "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": { @@ -3784,8 +2260,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3793,14 +2267,10 @@ }, "node_modules/eventemitter3": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3809,8 +2279,6 @@ }, "node_modules/express": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -3852,21 +2320,15 @@ }, "node_modules/extend": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, "license": "MIT" }, "node_modules/fault": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", - "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", "license": "MIT", "dependencies": { "format": "^0.2.0" @@ -3878,8 +2340,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3890,8 +2350,6 @@ }, "node_modules/finalhandler": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -3911,8 +2369,6 @@ }, "node_modules/follow-redirects": { "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -3931,8 +2387,6 @@ }, "node_modules/for-each": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { @@ -3947,8 +2401,6 @@ }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -3964,8 +2416,6 @@ }, "node_modules/form-data": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3980,8 +2430,6 @@ }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3989,8 +2437,6 @@ }, "node_modules/form-data/node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -4001,16 +2447,12 @@ }, "node_modules/format": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", "engines": { "node": ">=0.4.x" } }, "node_modules/formidable": { "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { @@ -4027,8 +2469,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4036,8 +2476,6 @@ }, "node_modules/fraction.js": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { @@ -4050,8 +2488,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -4059,10 +2495,7 @@ }, "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": [ @@ -4074,8 +2507,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4083,8 +2514,6 @@ }, "node_modules/functions-have-names": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", "funding": { @@ -4093,8 +2522,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -4103,8 +2530,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4127,8 +2552,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4140,8 +2563,6 @@ }, "node_modules/get-tsconfig": { "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", "dev": true, "license": "MIT", "dependencies": { @@ -4153,9 +2574,6 @@ }, "node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -4175,15 +2593,11 @@ }, "node_modules/glob/node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4192,8 +2606,6 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { @@ -4208,8 +2620,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4220,14 +2630,10 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/has-bigints": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", "engines": { @@ -4239,8 +2645,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -4249,8 +2653,6 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", "dependencies": { @@ -4262,8 +2664,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4274,8 +2674,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4289,8 +2687,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4301,8 +2697,6 @@ }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -4314,8 +2708,6 @@ }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -4341,8 +2733,6 @@ }, "node_modules/hast-util-whitespace": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -4354,8 +2744,6 @@ }, "node_modules/hastscript": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4371,8 +2759,6 @@ }, "node_modules/highlight.js": { "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "license": "BSD-3-Clause", "engines": { "node": "*" @@ -4380,14 +2766,10 @@ }, "node_modules/highlightjs-vue": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", - "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", "license": "CC0-1.0" }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4399,15 +2781,11 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/html-url-attributes": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", - "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -4416,8 +2794,6 @@ }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -4436,8 +2812,6 @@ }, "node_modules/http-proxy": { "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", @@ -4450,8 +2824,6 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -4464,8 +2836,6 @@ }, "node_modules/http-proxy-middleware": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", - "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.15", @@ -4481,8 +2851,6 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -4495,8 +2863,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4511,8 +2877,6 @@ }, "node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", "engines": { @@ -4521,20 +2885,14 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/inline-style-parser": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/internal-slot": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { @@ -4548,8 +2906,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -4557,8 +2913,6 @@ }, "node_modules/is-alphabetical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "license": "MIT", "funding": { "type": "github", @@ -4567,8 +2921,6 @@ }, "node_modules/is-alphanumerical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", @@ -4581,8 +2933,6 @@ }, "node_modules/is-arguments": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "dev": true, "license": "MIT", "dependencies": { @@ -4598,8 +2948,6 @@ }, "node_modules/is-array-buffer": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { @@ -4616,8 +2964,6 @@ }, "node_modules/is-bigint": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4632,8 +2978,6 @@ }, "node_modules/is-boolean-object": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { @@ -4649,8 +2993,6 @@ }, "node_modules/is-callable": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { @@ -4662,8 +3004,6 @@ }, "node_modules/is-date-object": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { @@ -4679,8 +3019,6 @@ }, "node_modules/is-decimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", "funding": { "type": "github", @@ -4689,8 +3027,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4698,8 +3034,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -4708,8 +3042,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4720,8 +3052,6 @@ }, "node_modules/is-hexadecimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", "funding": { "type": "github", @@ -4730,8 +3060,6 @@ }, "node_modules/is-map": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { @@ -4743,8 +3071,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4752,8 +3078,6 @@ }, "node_modules/is-number-object": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -4769,8 +3093,6 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { "node": ">=12" @@ -4781,8 +3103,6 @@ }, "node_modules/is-plain-object": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4790,21 +3110,15 @@ }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-regex": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { @@ -4822,8 +3136,6 @@ }, "node_modules/is-set": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { @@ -4835,8 +3147,6 @@ }, "node_modules/is-shared-array-buffer": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { @@ -4851,8 +3161,6 @@ }, "node_modules/is-string": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { @@ -4868,8 +3176,6 @@ }, "node_modules/is-symbol": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { @@ -4886,8 +3192,6 @@ }, "node_modules/is-weakmap": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { @@ -4899,8 +3203,6 @@ }, "node_modules/is-weakset": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4916,22 +3218,16 @@ }, "node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4940,8 +3236,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4955,8 +3249,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4970,8 +3262,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4984,8 +3274,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -5000,8 +3288,6 @@ }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -5010,14 +3296,10 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/jsdom": { "version": "24.1.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz", - "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5057,14 +3339,10 @@ }, "node_modules/json-schema": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/lightningcss": { "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", - "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -5091,32 +3369,8 @@ "lightningcss-win32-x64-msvc": "1.31.1" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-darwin-arm64": { "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", "cpu": [ "arm64" ], @@ -5135,215 +3389,13 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", - "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lodash": { "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", "funding": { "type": "github", @@ -5352,8 +3404,6 @@ }, "node_modules/loose-envify": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5364,15 +3414,11 @@ }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, "node_modules/lowlight": { "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", - "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", "license": "MIT", "dependencies": { "fault": "^1.0.0", @@ -5385,15 +3431,11 @@ }, "node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/lz-string": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", "bin": { @@ -5402,8 +3444,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5412,8 +3452,6 @@ }, "node_modules/magicast": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5424,8 +3462,6 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -5440,8 +3476,6 @@ }, "node_modules/markdown-table": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { "type": "github", @@ -5450,8 +3484,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5459,8 +3491,6 @@ }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5475,8 +3505,6 @@ }, "node_modules/mdast-util-from-markdown": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5499,8 +3527,6 @@ }, "node_modules/mdast-util-gfm": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -5518,8 +3544,6 @@ }, "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5535,8 +3559,6 @@ }, "node_modules/mdast-util-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5552,8 +3574,6 @@ }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5567,8 +3587,6 @@ }, "node_modules/mdast-util-gfm-table": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5584,8 +3602,6 @@ }, "node_modules/mdast-util-gfm-task-list-item": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5600,8 +3616,6 @@ }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -5618,8 +3632,6 @@ }, "node_modules/mdast-util-mdx-jsx": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -5642,8 +3654,6 @@ }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -5660,8 +3670,6 @@ }, "node_modules/mdast-util-newline-to-break": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", - "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5674,8 +3682,6 @@ }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5688,8 +3694,6 @@ }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -5709,8 +3713,6 @@ }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5730,8 +3732,6 @@ }, "node_modules/mdast-util-to-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" @@ -5743,8 +3743,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -5752,8 +3750,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -5764,8 +3760,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", "engines": { @@ -5774,8 +3768,6 @@ }, "node_modules/micromark": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { "type": "GitHub Sponsors", @@ -5809,8 +3801,6 @@ }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { "type": "GitHub Sponsors", @@ -5843,8 +3833,6 @@ }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", @@ -5863,8 +3851,6 @@ }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -5879,8 +3865,6 @@ }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5899,8 +3883,6 @@ }, "node_modules/micromark-extension-gfm-strikethrough": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5917,8 +3899,6 @@ }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5934,8 +3914,6 @@ }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -5947,8 +3925,6 @@ }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5964,8 +3940,6 @@ }, "node_modules/micromark-factory-destination": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -5985,8 +3959,6 @@ }, "node_modules/micromark-factory-label": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -6007,8 +3979,6 @@ }, "node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -6027,8 +3997,6 @@ }, "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -6049,8 +4017,6 @@ }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -6071,8 +4037,6 @@ }, "node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -6091,8 +4055,6 @@ }, "node_modules/micromark-util-chunked": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -6110,8 +4072,6 @@ }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -6131,8 +4091,6 @@ }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -6151,8 +4109,6 @@ }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -6170,8 +4126,6 @@ }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -6192,8 +4146,6 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -6208,8 +4160,6 @@ }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -6224,8 +4174,6 @@ }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -6243,8 +4191,6 @@ }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -6262,8 +4208,6 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -6283,8 +4227,6 @@ }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "funding": [ { "type": "GitHub Sponsors", @@ -6305,8 +4247,6 @@ }, "node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -6321,8 +4261,6 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { "type": "GitHub Sponsors", @@ -6337,8 +4275,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -6350,8 +4286,6 @@ }, "node_modules/mime": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", "bin": { @@ -6363,8 +4297,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6372,8 +4304,6 @@ }, "node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -6388,8 +4318,6 @@ }, "node_modules/min-indent": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", "engines": { @@ -6398,8 +4326,6 @@ }, "node_modules/minimatch": { "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6414,8 +4340,6 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -6424,14 +4348,10 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -6448,8 +4368,6 @@ }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6457,8 +4375,6 @@ }, "node_modules/next": { "version": "14.2.35", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", - "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", "license": "MIT", "dependencies": { "@next/env": "14.2.35", @@ -6507,8 +4423,6 @@ }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -6535,22 +4449,16 @@ }, "node_modules/node-releases": { "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nwsapi": { "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6561,8 +4469,6 @@ }, "node_modules/object-is": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6578,8 +4484,6 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "engines": { @@ -6588,8 +4492,6 @@ }, "node_modules/object.assign": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { @@ -6609,8 +4511,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -6621,8 +4521,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -6630,15 +4528,11 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parse-entities": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", @@ -6656,14 +4550,10 @@ }, "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, "node_modules/parse5": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, "license": "MIT", "dependencies": { @@ -6675,8 +4565,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6684,8 +4572,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -6694,8 +4580,6 @@ }, "node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6711,8 +4595,6 @@ }, "node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -6721,15 +4603,11 @@ }, "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" }, "node_modules/pathval": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -6738,14 +4616,10 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -6756,8 +4630,6 @@ }, "node_modules/possible-typed-array-names": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -6766,8 +4638,6 @@ }, "node_modules/postcss": { "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -6795,15 +4665,11 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true, "license": "MIT" }, "node_modules/pretty-format": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6817,8 +4683,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -6830,8 +4694,6 @@ }, "node_modules/prism-react-renderer": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", @@ -6843,8 +4705,6 @@ }, "node_modules/prismjs": { "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { "node": ">=6" @@ -6852,8 +4712,6 @@ }, "node_modules/property-information": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "license": "MIT", "funding": { "type": "github", @@ -6862,8 +4720,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -6875,14 +4731,10 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/psl": { "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", "dev": true, "license": "MIT", "dependencies": { @@ -6894,8 +4746,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -6904,8 +4754,6 @@ }, "node_modules/qs": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6919,15 +4767,11 @@ }, "node_modules/querystringify": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true, "license": "MIT" }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6935,8 +4779,6 @@ }, "node_modules/raw-body": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -6950,8 +4792,6 @@ }, "node_modules/react": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -6962,8 +4802,6 @@ }, "node_modules/react-dom": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -6975,15 +4813,11 @@ }, "node_modules/react-is": { "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, "license": "MIT" }, "node_modules/react-markdown": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -7009,8 +4843,6 @@ }, "node_modules/react-syntax-highlighter": { "version": "16.1.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", - "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", @@ -7029,8 +4861,6 @@ }, "node_modules/redent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "license": "MIT", "dependencies": { @@ -7043,8 +4873,6 @@ }, "node_modules/refractor": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", - "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -7059,8 +4887,6 @@ }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { @@ -7080,8 +4906,6 @@ }, "node_modules/remark-breaks": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", - "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7095,8 +4919,6 @@ }, "node_modules/remark-gfm": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7113,8 +4935,6 @@ }, "node_modules/remark-parse": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7129,8 +4949,6 @@ }, "node_modules/remark-rehype": { "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -7146,8 +4964,6 @@ }, "node_modules/remark-stringify": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7161,8 +4977,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -7171,14 +4985,10 @@ }, "node_modules/requires-port": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "license": "MIT" }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", "funding": { @@ -7187,8 +4997,6 @@ }, "node_modules/rollup": { "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -7232,8 +5040,6 @@ }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -7248,15 +5054,11 @@ }, "node_modules/rrweb-cssom": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true, "license": "MIT" }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7265,8 +5067,6 @@ }, "node_modules/safe-regex-test": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { @@ -7283,14 +5083,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/saxes": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "license": "ISC", "dependencies": { @@ -7302,8 +5098,6 @@ }, "node_modules/scheduler": { "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -7311,8 +5105,6 @@ }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7324,8 +5116,6 @@ }, "node_modules/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -7350,8 +5140,6 @@ }, "node_modules/serve-static": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -7369,8 +5157,6 @@ }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -7387,8 +5173,6 @@ }, "node_modules/set-function-name": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7403,14 +5187,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -7422,8 +5202,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -7432,8 +5210,6 @@ }, "node_modules/shell-quote": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", "engines": { @@ -7445,8 +5221,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7464,8 +5238,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7480,8 +5252,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7498,8 +5268,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7517,15 +5285,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -7537,15 +5301,11 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, "license": "MIT" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7553,8 +5313,6 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", @@ -7563,21 +5321,15 @@ }, "node_modules/spawn-command": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7585,15 +5337,11 @@ }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7606,16 +5354,12 @@ }, "node_modules/streamsearch": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { "node": ">=10.0.0" } }, "node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -7633,8 +5377,6 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -7648,15 +5390,11 @@ }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -7668,8 +5406,6 @@ }, "node_modules/stringify-entities": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -7682,8 +5418,6 @@ }, "node_modules/strip-ansi": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { @@ -7699,8 +5433,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -7712,8 +5444,6 @@ }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -7725,8 +5455,6 @@ }, "node_modules/strip-indent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7738,8 +5466,6 @@ }, "node_modules/strip-literal": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { @@ -7751,15 +5477,11 @@ }, "node_modules/strip-literal/node_modules/js-tokens": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" }, "node_modules/style-to-js": { "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { "style-to-object": "1.0.14" @@ -7767,8 +5489,6 @@ }, "node_modules/style-to-object": { "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { "inline-style-parser": "0.2.7" @@ -7776,8 +5496,6 @@ }, "node_modules/styled-jsx": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -7799,8 +5517,6 @@ }, "node_modules/superagent": { "version": "10.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", - "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7820,8 +5536,6 @@ }, "node_modules/supertest": { "version": "7.2.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", - "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { @@ -7835,8 +5549,6 @@ }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -7848,22 +5560,16 @@ }, "node_modules/symbol-tree": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, "node_modules/tailwindcss": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", - "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { @@ -7876,8 +5582,6 @@ }, "node_modules/test-exclude": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { @@ -7891,22 +5595,16 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7922,8 +5620,6 @@ }, "node_modules/tinyglobby/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": { @@ -7940,8 +5636,6 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -7953,8 +5647,6 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -7963,8 +5655,6 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -7973,8 +5663,6 @@ }, "node_modules/tinyspy": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -7983,8 +5671,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7995,8 +5681,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -8004,8 +5688,6 @@ }, "node_modules/tough-cookie": { "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8020,8 +5702,6 @@ }, "node_modules/tr46": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { @@ -8033,8 +5713,6 @@ }, "node_modules/tree-kill": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -8043,8 +5721,6 @@ }, "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -8053,8 +5729,6 @@ }, "node_modules/trough": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", @@ -8063,14 +5737,10 @@ }, "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==", "license": "0BSD" }, "node_modules/tsx": { "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { @@ -8089,8 +5759,6 @@ }, "node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -8101,8 +5769,6 @@ }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -8115,8 +5781,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8129,14 +5793,10 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/unified": { "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8154,8 +5814,6 @@ }, "node_modules/unist-util-is": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -8167,8 +5825,6 @@ }, "node_modules/unist-util-position": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -8180,8 +5836,6 @@ }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -8193,8 +5847,6 @@ }, "node_modules/unist-util-visit": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8208,8 +5860,6 @@ }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8222,8 +5872,6 @@ }, "node_modules/universalify": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "license": "MIT", "engines": { @@ -8232,8 +5880,6 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8241,8 +5887,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -8272,8 +5916,6 @@ }, "node_modules/url-parse": { "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8283,8 +5925,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8292,8 +5932,6 @@ }, "node_modules/vfile": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8306,8 +5944,6 @@ }, "node_modules/vfile-message": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8320,8 +5956,6 @@ }, "node_modules/vite": { "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { @@ -8395,8 +6029,6 @@ }, "node_modules/vite-node": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { @@ -8418,8 +6050,6 @@ }, "node_modules/vite/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": { @@ -8436,8 +6066,6 @@ }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -8449,8 +6077,6 @@ }, "node_modules/vitest": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { @@ -8522,8 +6148,6 @@ }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -8535,8 +6159,6 @@ }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", "dependencies": { @@ -8548,8 +6170,6 @@ }, "node_modules/webidl-conversions": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -8558,9 +6178,6 @@ }, "node_modules/whatwg-encoding": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "dev": true, "license": "MIT", "dependencies": { @@ -8572,8 +6189,6 @@ }, "node_modules/whatwg-encoding/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -8585,8 +6200,6 @@ }, "node_modules/whatwg-mimetype": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { @@ -8595,8 +6208,6 @@ }, "node_modules/whatwg-url": { "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", "dependencies": { @@ -8609,8 +6220,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -8625,8 +6234,6 @@ }, "node_modules/which-boxed-primitive": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "license": "MIT", "dependencies": { @@ -8645,8 +6252,6 @@ }, "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", "dependencies": { @@ -8664,8 +6269,6 @@ }, "node_modules/which-typed-array": { "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -8686,8 +6289,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -8703,8 +6304,6 @@ }, "node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8722,8 +6321,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8740,15 +6337,11 @@ }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -8762,8 +6355,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -8775,8 +6366,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -8788,14 +6377,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -8815,8 +6400,6 @@ }, "node_modules/xml-name-validator": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -8825,15 +6408,11 @@ }, "node_modules/xmlchars": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -8842,8 +6421,6 @@ }, "node_modules/yaml": { "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -8857,8 +6434,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -8876,8 +6451,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -8886,15 +6459,11 @@ }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -8908,8 +6477,6 @@ }, "node_modules/yargs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -8921,8 +6488,6 @@ }, "node_modules/zod": { "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -8930,8 +6495,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { "zod": "^3.25 || ^4" @@ -8939,8 +6502,6 @@ }, "node_modules/zwitch": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", @@ -8994,13 +6555,10 @@ "license": "Apache-2.0", "dependencies": { "@agent-relay/config": "^3.2.5", - "@agent-relay/protocol": "^2.3.14", "@agent-relay/sdk": "^3.2.5", - "@agent-relay/storage": "^2.3.14", "@agent-relay/trajectory": "^3.2.5", - "@agent-relay/utils": "^3.2.5", - "@relaycast/sdk": "^0.5.1", - "@relaycast/types": "^0.5.1", + "@relaycast/sdk": "0.5.2", + "@relaycast/types": "0.5.2", "express": "^5.2.1", "http-proxy-middleware": "^3.0.0", "ws": "^8.18.3" @@ -9024,18 +6582,20 @@ } }, "packages/dashboard-server/node_modules/@relaycast/sdk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-0.5.1.tgz", - "integrity": "sha512-BGmk4dfig1SBOK6HDGGS7/t4Kek9MC8mCyJLVpwQ8jIg6552j6iXMQGiirj95H78RqghPfAx3cjPNsTJ+uCcrw==", + "version": "0.5.2", + "dependencies": { + "@relaycast/types": "0.5.2", + "zod": "^4.3.6" + } + }, + "packages/dashboard-server/node_modules/@relaycast/types": { + "version": "0.5.2", "dependencies": { - "@relaycast/types": "0.5.1", "zod": "^4.3.6" } }, "packages/dashboard-server/node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -9043,8 +6603,6 @@ }, "packages/dashboard/node_modules/@relaycast/sdk": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@relaycast/sdk/-/sdk-0.5.1.tgz", - "integrity": "sha512-BGmk4dfig1SBOK6HDGGS7/t4Kek9MC8mCyJLVpwQ8jIg6552j6iXMQGiirj95H78RqghPfAx3cjPNsTJ+uCcrw==", "dependencies": { "@relaycast/types": "0.5.1", "zod": "^4.3.6" @@ -9052,8 +6610,6 @@ }, "packages/dashboard/node_modules/concurrently": { "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, "license": "MIT", "dependencies": { @@ -9077,8 +6633,6 @@ }, "packages/dashboard/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9093,8 +6647,6 @@ }, "packages/dashboard/node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/packages/dashboard-server/package.json b/packages/dashboard-server/package.json index a7f8564..cbdad21 100644 --- a/packages/dashboard-server/package.json +++ b/packages/dashboard-server/package.json @@ -34,13 +34,10 @@ }, "dependencies": { "@agent-relay/config": "^3.2.5", - "@agent-relay/protocol": "^2.3.14", "@agent-relay/sdk": "^3.2.5", - "@agent-relay/storage": "^2.3.14", "@agent-relay/trajectory": "^3.2.5", - "@agent-relay/utils": "^3.2.5", - "@relaycast/sdk": "^0.5.1", - "@relaycast/types": "^0.5.1", + "@relaycast/sdk": "0.5.2", + "@relaycast/types": "0.5.2", "express": "^5.2.1", "http-proxy-middleware": "^3.0.0", "ws": "^8.18.3" diff --git a/packages/dashboard-server/src/lib/attachment-storage.ts b/packages/dashboard-server/src/lib/attachment-storage.ts deleted file mode 100644 index d111199..0000000 --- a/packages/dashboard-server/src/lib/attachment-storage.ts +++ /dev/null @@ -1,64 +0,0 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; - -export interface AttachmentStorageSetup { - attachmentsDir: string; - uploadsDir: string; - stopEviction: () => void; -} - -/** - * Prepare uploads/attachments directories and start periodic attachment eviction. - */ -export function initializeAttachmentStorage(dataDir: string): AttachmentStorageSetup { - const attachmentsDir = path.join(os.homedir(), '.relay', 'attachments'); - if (!fs.existsSync(attachmentsDir)) { - fs.mkdirSync(attachmentsDir, { recursive: true }); - } - - const uploadsDir = path.join(dataDir, 'uploads'); - if (!fs.existsSync(uploadsDir)) { - fs.mkdirSync(uploadsDir, { recursive: true }); - } - - const maxAgeMs = 7 * 24 * 60 * 60 * 1000; - - const evictOldAttachments = async () => { - try { - const files = await fs.promises.readdir(attachmentsDir); - const now = Date.now(); - let evictedCount = 0; - - for (const file of files) { - const filePath = path.join(attachmentsDir, file); - try { - const stat = await fs.promises.stat(filePath); - if (stat.isFile() && now - stat.mtimeMs > maxAgeMs) { - await fs.promises.unlink(filePath); - evictedCount++; - } - } catch { - // Ignore per-file errors (deleted concurrently, permission changes, etc). - } - } - - if (evictedCount > 0) { - console.log(`[dashboard] Evicted ${evictedCount} old attachment(s)`); - } - } catch (err) { - console.error('[dashboard] Failed to evict old attachments:', err); - } - }; - - void evictOldAttachments(); - const evictionInterval = setInterval(() => { - void evictOldAttachments(); - }, 60 * 60 * 1000); - - return { - attachmentsDir, - uploadsDir, - stopEviction: () => clearInterval(evictionInterval), - }; -} diff --git a/packages/dashboard-server/src/lib/broadcast.ts b/packages/dashboard-server/src/lib/broadcast.ts deleted file mode 100644 index 817b201..0000000 --- a/packages/dashboard-server/src/lib/broadcast.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { WebSocket } from 'ws'; -import type { ServerState } from './server-state.js'; - -export interface BroadcastDependencies { - getAllData: () => Promise; - getBridgeData: () => Promise; - debug?: (message: string) => void; -} - -export interface Broadcasters { - broadcastData: () => Promise; - broadcastBridgeData: () => Promise; - broadcastPresence: (message: object, exclude?: WebSocket) => void; - broadcastLogOutput: (agentName: string, output: string) => void; -} - -/** - * Build reusable broadcaster functions backed by shared ServerState. - */ -export function createBroadcasters( - state: ServerState, - deps: BroadcastDependencies, -): Broadcasters { - let lastBroadcastPayload = ''; - let lastBridgeBroadcastPayload = ''; - - const broadcastData = async (): Promise => { - try { - const data = await deps.getAllData(); - - // Skip broadcast when team data is temporarily unavailable. - if (!data) { - return; - } - - const rawPayload = JSON.stringify(data); - if (!rawPayload || rawPayload.length === 0) { - console.warn('[dashboard] Skipping broadcast - empty payload'); - return; - } - - if (rawPayload === lastBroadcastPayload) { - return; - } - lastBroadcastPayload = rawPayload; - - const seq = state.mainMessageBuffer.push('data', rawPayload); - const payload = JSON.stringify( - typeof data === 'object' && data !== null - ? { seq, ...(data as Record) } - : { seq, data }, - ); - - state.refs.wss.clients.forEach((client) => { - // Skip clients still being initialized by the connection handler. - if (state.initializingClients.has(client)) { - return; - } - if (client.readyState === WebSocket.OPEN) { - try { - client.send(payload); - } catch (err) { - console.error('[dashboard] Failed to send to client:', err); - } - } - }); - } catch (err) { - console.error('[dashboard] Failed to broadcast data:', err); - } - }; - - const broadcastBridgeData = async (): Promise => { - try { - const data = await deps.getBridgeData(); - const payload = JSON.stringify(data); - - if (!payload || payload.length === 0) { - console.warn('[dashboard] Skipping bridge broadcast - empty payload'); - return; - } - - if (payload === lastBridgeBroadcastPayload) { - return; - } - lastBridgeBroadcastPayload = payload; - - state.refs.wssBridge.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - try { - client.send(payload); - } catch (err) { - console.error('[dashboard] Failed to send to bridge client:', err); - } - } - }); - } catch (err) { - console.error('[dashboard] Failed to broadcast bridge data:', err); - } - }; - - const broadcastPresence = (message: object, exclude?: WebSocket): void => { - const payload = JSON.stringify(message); - state.refs.wssPresence.clients.forEach((client) => { - if (client !== exclude && client.readyState === WebSocket.OPEN) { - client.send(payload); - } - }); - }; - - const broadcastLogOutput = (agentName: string, output: string): void => { - const clients = state.logSubscriptions.get(agentName); - if (output.length === 0) return; - - const basePayload = { - type: 'output', - agent: agentName, - data: output, - content: output, - timestamp: new Date().toISOString(), - }; - const serializedBase = JSON.stringify(basePayload); - const seq = state.getAgentLogBuffer(agentName).push('output', serializedBase); - const payload = JSON.stringify({ - ...basePayload, - seq, - }); - - if (!clients || clients.size === 0) { - return; - } - - for (const client of clients) { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - } - - if (deps.debug) { - deps.debug(`[dashboard] Broadcasted log output for ${agentName} to ${clients.size} client(s)`); - } - }; - - return { - broadcastData, - broadcastBridgeData, - broadcastPresence, - broadcastLogOutput, - }; -} diff --git a/packages/dashboard-server/src/lib/channel-state.ts b/packages/dashboard-server/src/lib/channel-state.ts deleted file mode 100644 index 57be172..0000000 --- a/packages/dashboard-server/src/lib/channel-state.ts +++ /dev/null @@ -1,194 +0,0 @@ -import crypto from 'crypto'; -import type { StorageAdapter } from '@agent-relay/storage/adapter'; - -export interface ChannelRecord { - id: string; - visibility: 'public' | 'private'; - status: 'active' | 'archived'; - createdAt?: number; - createdBy?: string; - description?: string; - lastActivityAt: number; - lastMessage?: { content: string; from: string; timestamp: string }; - members: Set; - dmParticipants?: string[]; -} - -interface ChannelPersistenceOptions { - storage?: StorageAdapter; - defaultWorkspaceId?: string; -} - -interface PersistMembershipOptions { - invitedBy?: string; - workspaceId?: string; -} - -export function createChannelPersistence(options: ChannelPersistenceOptions) { - const { storage, defaultWorkspaceId } = options; - - const loadChannelRecords = async (workspaceId?: string): Promise> => { - const map = new Map(); - if (!storage) { - return map; - } - - const stored = await storage.getMessages({ order: 'asc' }); - - const ensureRecord = (id: string): ChannelRecord => { - let record = map.get(id); - if (!record) { - record = { - id, - visibility: 'public', - status: 'active', - lastActivityAt: 0, - members: new Set(), - }; - if (id.startsWith('dm:')) { - const participants = id.split(':').slice(1).filter(Boolean); - if (participants.length > 0) { - participants.forEach((participant) => record!.members.add(participant)); - record.dmParticipants = participants; - } - } - map.set(id, record); - } - return record; - }; - - const addMember = (record: ChannelRecord, member: string) => { - if (!member) return; - record.members.add(member); - }; - - for (const msg of stored) { - const target = msg.to; - if (!target || (!target.startsWith('#') && !target.startsWith('dm:'))) { - continue; - } - - const data = msg.data as Record | undefined; - const messageWorkspaceId = typeof data?._workspaceId === 'string' ? data._workspaceId : undefined; - if (workspaceId && messageWorkspaceId && messageWorkspaceId !== workspaceId) { - continue; - } - - const record = ensureRecord(target); - const timestamp = typeof msg.ts === 'number' ? msg.ts : Date.now(); - - const channelCreate = data?._channelCreate as { createdBy?: string; description?: string; isPrivate?: boolean } | undefined; - if (channelCreate) { - record.createdAt = record.createdAt ?? timestamp; - record.createdBy = channelCreate.createdBy ?? record.createdBy; - if (channelCreate.description) { - record.description = String(channelCreate.description); - } - record.visibility = channelCreate.isPrivate ? 'private' : 'public'; - } - - const stateChange = data?._channelState as string | undefined; - if (stateChange) { - record.status = stateChange === 'archived' ? 'archived' : 'active'; - record.lastActivityAt = Math.max(record.lastActivityAt, timestamp); - } - - const membership = data?._channelMembership as { member?: string; action?: string } | undefined; - if (membership?.member) { - if (membership.action === 'leave') { - record.members.delete(membership.member); - } else { - addMember(record, membership.member); - } - record.lastActivityAt = Math.max(record.lastActivityAt, timestamp); - } - - const isChannelMessage = Boolean(data?._isChannelMessage); - if (isChannelMessage) { - addMember(record, msg.from); - record.lastActivityAt = Math.max(record.lastActivityAt, timestamp); - record.lastMessage = { - content: msg.body, - from: msg.from || '__system__', - timestamp: new Date(timestamp).toISOString(), - }; - - if (target.startsWith('dm:') && !record.dmParticipants) { - const participants = target.split(':').slice(1).filter(Boolean); - if (participants.length > 0) { - participants.forEach((participant) => record.members.add(participant)); - record.dmParticipants = participants; - } - } - } - } - - return map; - }; - - const loadPersistedChannelsForUser = async (username: string, workspaceId?: string): Promise => { - const channelMap = await loadChannelRecords(workspaceId); - const result: string[] = []; - - for (const record of channelMap.values()) { - if (record.status === 'archived') { - continue; - } - if (record.members.has(username)) { - result.push(record.id); - } - } - - if (!result.includes('#general')) { - result.unshift('#general'); - } - - return result; - }; - - const persistChannelMembershipEvent = async ( - channel: string, - member: string, - action: 'join' | 'leave' | 'invite', - eventOptions?: PersistMembershipOptions, - ) => { - if (!storage) return; - - const data: Record = { - _channelMembership: { - member, - action, - invitedBy: eventOptions?.invitedBy, - }, - }; - - const workspaceToStore = eventOptions?.workspaceId ?? defaultWorkspaceId; - if (workspaceToStore) { - data._workspaceId = workspaceToStore; - } - - await storage - .saveMessage({ - id: `channel-membership-${crypto.randomUUID()}`, - ts: Date.now(), - from: '__system__', - to: channel, - topic: undefined, - kind: 'state', - body: `${action}:${member}`, - data, - status: 'read', - is_urgent: false, - is_broadcast: true, - }) - .catch((err) => { - console.error('[channels] Failed to persist membership event', err); - }); - }; - - return { - loadChannelRecords, - loadPersistedChannelsForUser, - persistChannelMembershipEvent, - }; -} diff --git a/packages/dashboard-server/src/lib/cli-auth.ts b/packages/dashboard-server/src/lib/cli-auth.ts deleted file mode 100644 index d9d974a..0000000 --- a/packages/dashboard-server/src/lib/cli-auth.ts +++ /dev/null @@ -1,214 +0,0 @@ -import crypto from 'crypto'; -import { getSupportedProviders as getConfiguredProviders } from '@agent-relay/config/cli-auth-config'; - -export interface StartCLIAuthOptions { - useDeviceFlow?: boolean; - userId?: string; -} - -export interface AuthSession { - id: string; - provider: string; - userId?: string; - status: 'starting' | 'waiting_auth' | 'success' | 'error'; - authUrl?: string; - token?: string; - refreshToken?: string; - tokenExpiresAt?: Date; - error?: string; - createdAt: Date; - updatedAt: Date; -} - -interface SubmitAuthCodeResult { - success: boolean; - error?: string; - needsRestart?: boolean; -} - -interface CompleteAuthResult { - success: boolean; - error?: string; - token?: string; -} - -interface SupportedProvider { - id: string; - displayName: string; - command: string; -} - -interface DaemonCLIAuthApi { - startCLIAuth(provider: string, options?: StartCLIAuthOptions): Promise; - getAuthSession(sessionId: string): AuthSession | null; - cancelAuthSession(sessionId: string): boolean; - submitAuthCode(sessionId: string, code: string): Promise; - completeAuthSession(sessionId: string): Promise; - getSupportedProviders(): SupportedProvider[]; -} - -const sessions = new Map(); -let daemonApi: DaemonCLIAuthApi | null = null; -let daemonApiLoadPromise: Promise | undefined; -const daemonModuleId = '@agent-relay/daemon'; - -function getFallbackProviders(): SupportedProvider[] { - try { - const providers = getConfiguredProviders(); - if (Array.isArray(providers) && providers.length > 0) { - return providers as SupportedProvider[]; - } - } catch { - // Ignore and use static fallback below. - } - - return [ - { id: 'claude', displayName: 'Claude', command: 'claude' }, - { id: 'openai', displayName: 'OpenAI / Codex', command: 'codex' }, - { id: 'gemini', displayName: 'Gemini', command: 'gemini' }, - ]; -} - -async function loadDaemonApi(): Promise { - if (daemonApi) { - return daemonApi; - } - - if (!daemonApiLoadPromise) { - daemonApiLoadPromise = import(daemonModuleId) - .then((mod) => { - if ( - typeof mod.startCLIAuth === 'function' && - typeof mod.getAuthSession === 'function' && - typeof mod.cancelAuthSession === 'function' && - typeof mod.submitAuthCode === 'function' && - typeof mod.completeAuthSession === 'function' && - typeof mod.getSupportedProviders === 'function' - ) { - daemonApi = { - startCLIAuth: mod.startCLIAuth as DaemonCLIAuthApi['startCLIAuth'], - getAuthSession: mod.getAuthSession as DaemonCLIAuthApi['getAuthSession'], - cancelAuthSession: mod.cancelAuthSession as DaemonCLIAuthApi['cancelAuthSession'], - submitAuthCode: mod.submitAuthCode as DaemonCLIAuthApi['submitAuthCode'], - completeAuthSession: mod.completeAuthSession as DaemonCLIAuthApi['completeAuthSession'], - getSupportedProviders: mod.getSupportedProviders as DaemonCLIAuthApi['getSupportedProviders'], - }; - } - }) - .catch(() => { - // Daemon package isn't present in this workspace; use fallback implementation. - }) - .then(() => undefined); - } - - await daemonApiLoadPromise; - return daemonApi; -} - -function touchSession(session: AuthSession): void { - session.updatedAt = new Date(); - sessions.set(session.id, session); -} - -export async function startCLIAuth(provider: string, options?: StartCLIAuthOptions): Promise { - const api = await loadDaemonApi(); - if (api) { - return api.startCLIAuth(provider, options); - } - - const supportedProviders = getFallbackProviders(); - if (!supportedProviders.some((p) => p.id === provider)) { - throw new Error(`Unsupported provider: ${provider}`); - } - - const now = new Date(); - const session: AuthSession = { - id: crypto.randomUUID(), - provider, - userId: options?.userId, - status: 'waiting_auth', - createdAt: now, - updatedAt: now, - }; - - sessions.set(session.id, session); - return session; -} - -export function getAuthSession(sessionId: string): AuthSession | null { - if (daemonApi) { - return daemonApi.getAuthSession(sessionId); - } - return sessions.get(sessionId) ?? null; -} - -export function cancelAuthSession(sessionId: string): boolean { - if (daemonApi) { - return daemonApi.cancelAuthSession(sessionId); - } - return sessions.delete(sessionId); -} - -export async function submitAuthCode(sessionId: string, code: string): Promise { - const api = await loadDaemonApi(); - if (api) { - return api.submitAuthCode(sessionId, code); - } - - const session = sessions.get(sessionId); - if (!session) { - return { - success: false, - error: 'Session not found', - needsRestart: true, - }; - } - - if (!code.trim()) { - session.status = 'error'; - session.error = 'Auth code is required'; - touchSession(session); - return { - success: false, - error: session.error, - needsRestart: false, - }; - } - - session.status = 'success'; - session.token = code.trim(); - session.tokenExpiresAt = new Date(Date.now() + 60 * 60 * 1000); - session.error = undefined; - touchSession(session); - - return { success: true }; -} - -export async function completeAuthSession(sessionId: string): Promise { - const api = await loadDaemonApi(); - if (api) { - return api.completeAuthSession(sessionId); - } - - const session = sessions.get(sessionId); - if (!session) { - return { success: false, error: 'Session not found' }; - } - - if (session.status === 'success') { - return { success: true, token: session.token }; - } - - if (session.status === 'error') { - return { success: false, error: session.error ?? 'Authentication failed' }; - } - - return { success: false, error: 'Authentication still in progress' }; -} - -export function getSupportedProviders(): SupportedProvider[] { - if (daemonApi) { - return daemonApi.getSupportedProviders(); - } - return getFallbackProviders(); -} diff --git a/packages/dashboard-server/src/lib/data-assembly.ts b/packages/dashboard-server/src/lib/data-assembly.ts deleted file mode 100644 index 5ec3bf3..0000000 --- a/packages/dashboard-server/src/lib/data-assembly.ts +++ /dev/null @@ -1,748 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import crypto from 'crypto'; -import { type StorageAdapter, type StoredMessage } from '@agent-relay/storage/adapter'; -import { loadTeamsConfig } from '@agent-relay/config'; -import type { ThreadMetadata } from '../types/threading.js'; -import { fetchCloudNeedsAttention, parseNeedsAttentionAgents } from '../services/needs-attention.js'; - -interface AgentStatus { - name: string; - role: string; - cli: string; - messageCount: number; - status?: string; - lastActive?: string; - lastSeen?: string; - needsAttention?: boolean; - isProcessing?: boolean; - processingStartedAt?: number; - isSpawned?: boolean; - team?: string; - avatarUrl?: string; - model?: string; - cwd?: string; -} - -interface Attachment { - id: string; - filename: string; - mimeType: string; - size: number; - url: string; - filePath?: string; - width?: number; - height?: number; - data?: string; -} - -interface Message { - from: string; - to: string; - content: string; - timestamp: string; - id: string; - thread?: string; - isBroadcast?: boolean; - status?: string; - attachments?: Attachment[]; - channel?: string; - replyCount?: number; - threadSummary?: ThreadMetadata; -} - -interface SessionInfo { - id: string; - agentName: string; - cli?: string; - startedAt: string; - endedAt?: string; - duration?: string; - messageCount: number; - summary?: string; - isActive: boolean; - closedBy?: 'agent' | 'disconnect' | 'error'; -} - -interface AgentSummary { - agentName: string; - lastUpdated: string; - currentTask?: string; - completedTasks?: string[]; - context?: string; -} - -interface TeamAgent { - name: string; - role: string; - cli?: string; - lastSeen?: string; - lastActive?: string; - team?: string; -} - -interface TeamData { - agents: TeamAgent[]; -} - -interface RemoteUserRecord { - name: string; - avatarUrl?: string; - lastSeen?: string; - connectedAt?: string; -} - -interface PresenceInfo { - avatarUrl?: string; - lastSeen: string; -} - -interface PresenceStateLike { - info: PresenceInfo; -} - -interface ActiveWorkerLike { - name: string; - team?: string; - cli?: string; - cwd?: string; -} - -interface SpawnReaderLike { - getActiveWorkers: () => ActiveWorkerLike[]; -} - -interface BridgeLead { - name: string; - connected?: boolean; -} - -interface BridgeAgent { - name: string; - status: string; - cli?: string; - lastSeen?: string; -} - -interface BridgeProject { - path?: string; - lead?: BridgeLead; - agents?: BridgeAgent[]; -} - -interface BridgeData { - projects: BridgeProject[]; - messages: unknown[]; - connected: boolean; -} - -export interface DataAssemblyDeps { - dataDir: string; - teamDir: string; - projectRoot?: string; - defaultWorkspaceId?: string; - storage?: StorageAdapter; - spawnReader?: SpawnReaderLike; - onlineUsers: Map; - agentCwdMap: Map; - debug?: (message: string) => void; -} - -export interface DataAssembly { - getAllData: () => Promise<{ - agents: Array; - users: Array; - messages: Message[]; - activity: Message[]; - sessions: SessionInfo[]; - summaries: AgentSummary[]; - } | null>; - getBridgeData: () => Promise; - isInternalAgent: (name: string) => boolean; - remapAgentName: (name: string) => string; - buildThreadSummaryMap: (rows: StoredMessage[]) => Map; - formatDuration: (startMs: number, endMs?: number) => string; -} - -// Helper to check if an agent name is internal/system (should be hidden from UI). -// Convention: agent names starting with __ are internal (e.g., __spawner__, __DashboardBridge__). -export const isInternalAgent = (name: string): boolean => { - if (name === '__cli_sender__') return false; - return name.startsWith('__'); -}; - -// Display-name remapping for CLI sender (used across message and history endpoints). -export const remapAgentName = (name: string): string => { - if (name === '__cli_sender__') return 'CLI'; - return name; -}; - -export const buildThreadSummaryMap = (rows: StoredMessage[]): Map => { - const summaries = new Map(); - - for (const row of rows) { - if (!row.thread) { - continue; - } - - const threadId = row.thread; - const existing = summaries.get(threadId); - const participants = existing ? new Set(existing.participants) : new Set(); - participants.add(row.from); - - const isNewer = !existing || row.ts >= existing.lastReplyAt; - summaries.set(threadId, { - threadId, - replyCount: existing ? existing.replyCount + 1 : 1, - participants: Array.from(participants), - lastReplyAt: isNewer ? row.ts : existing.lastReplyAt, - lastReplyPreview: isNewer ? row.body : existing.lastReplyPreview, - }); - } - - return summaries; -}; - -const mapStoredMessages = (rows: StoredMessage[], threadSummaries?: Map): Message[] => rows - // Filter out messages from/to internal system agents (e.g., __spawner__). - .filter((row) => !isInternalAgent(row.from) && !isInternalAgent(row.to)) - // Filter out channel messages - these are shown in the channels view, not the agent messages view. - .filter((row) => { - if (row.data && typeof row.data === 'object' && '_isChannelMessage' in row.data) { - return false; - } - return true; - }) - .map((row) => { - const summaryFromReplies = threadSummaries?.get(row.id); - const fallbackSummary = (!summaryFromReplies && row.replyCount && row.replyCount > 0) - ? { - threadId: row.id, - replyCount: row.replyCount, - participants: Array.from(new Set([row.from, row.to])), - lastReplyAt: row.ts, - } - : undefined; - const threadSummary = summaryFromReplies ?? fallbackSummary; - let attachments: Attachment[] | undefined; - let channel: string | undefined; - let effectiveFrom = row.from; - let effectiveTo = row.to; - - if (row.data && typeof row.data === 'object') { - if ('attachments' in row.data) { - attachments = (row.data as { attachments: Attachment[] }).attachments; - } - if ('channel' in row.data) { - channel = (row.data as { channel: string }).channel; - } - // For dashboard messages sent via Dashboard, use the actual sender name. - if ('senderName' in row.data && row.from === 'Dashboard') { - effectiveFrom = (row.data as { senderName: string }).senderName; - } - } - - effectiveFrom = remapAgentName(effectiveFrom); - effectiveTo = remapAgentName(effectiveTo); - - return { - from: effectiveFrom, - to: effectiveTo, - content: row.body, - timestamp: new Date(row.ts).toISOString(), - id: row.id, - thread: row.thread, - isBroadcast: row.is_broadcast, - replyCount: threadSummary?.replyCount ?? row.replyCount, - threadSummary, - status: row.status, - attachments, - channel, - }; - }); - -export const formatDuration = (startMs: number, endMs?: number): string => { - const end = endMs ?? Date.now(); - const durationMs = end - startMs; - const minutes = Math.floor(durationMs / 60000); - const hours = Math.floor(minutes / 60); - if (hours > 0) { - return `${hours}h ${minutes % 60}m`; - } - return `${minutes}m`; -}; - -export function createDataAssembly(deps: DataAssemblyDeps): DataAssembly { - const { - dataDir, - teamDir, - projectRoot, - defaultWorkspaceId, - storage, - spawnReader, - onlineUsers, - agentCwdMap, - debug, - } = deps; - - const getTeamData = (): TeamData | null => { - // Try team.json first (file-based team mode). - const teamPath = path.join(teamDir, 'team.json'); - if (fs.existsSync(teamPath)) { - try { - return JSON.parse(fs.readFileSync(teamPath, 'utf-8')) as TeamData; - } catch (e) { - console.error('Failed to read team.json', e); - } - } - - // Fall back to agents.json (daemon mode - live connected agents). - const agentsPath = path.join(teamDir, 'agents.json'); - if (fs.existsSync(agentsPath)) { - try { - const data = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')) as { agents: Array<{ name: string; connectedAt?: string; cli?: string; lastSeen?: string; team?: string }> }; - // Convert agents.json format to team.json format. - return { - agents: data.agents.map((a) => ({ - name: a.name, - role: 'Agent', - cli: a.cli ?? 'Unknown', - lastSeen: a.lastSeen ?? a.connectedAt, - lastActive: a.lastSeen ?? a.connectedAt, - team: a.team, - })), - }; - } catch (e) { - console.error('Failed to read agents.json', e); - } - } - - return null; - }; - - const parseInbox = (agentName: string): Message[] => { - const inboxPath = path.join(dataDir, agentName, 'inbox.md'); - if (!fs.existsSync(inboxPath)) return []; - - try { - const content = fs.readFileSync(inboxPath, 'utf-8'); - const messages: Message[] = []; - - // Split by "## Message from ". - const parts = content.split('## Message from '); - - parts.forEach((part, index) => { - if (!part.trim()) return; - - const firstLineEnd = part.indexOf('\n'); - if (firstLineEnd === -1) return; - - const header = part.substring(0, firstLineEnd).trim(); - const body = part.substring(firstLineEnd).trim(); - - let sender = header; - let timestamp = new Date().toISOString(); - - if (header.includes('|')) { - const split = header.split('|'); - sender = split[0].trim(); - timestamp = split.slice(1).join('|').trim(); - } - - messages.push({ - from: sender, - to: agentName, - content: body, - timestamp, - id: `${agentName}-${index}-${Date.now()}`, - }); - }); - return messages; - } catch (e) { - console.error(`Failed to read inbox for ${agentName}`, e); - return []; - } - }; - - const getMessages = async (agents: TeamAgent[]): Promise => { - // For local mode: use storage (SQLite) first - faster and avoids daemon query timeouts. - if (storage) { - const rows = await storage.getMessages({ limit: 100, order: 'desc' }); - const threadSummaries = buildThreadSummaryMap(rows); - // Dashboard expects oldest first. - return mapStoredMessages(rows, threadSummaries).reverse(); - } - - // Final fallback to file-based inbox parsing. - let allMessages: Message[] = []; - agents.forEach((a) => { - const msgs = parseInbox(a.name); - allMessages = [...allMessages, ...msgs]; - }); - return allMessages; - }; - - const getRecentSessions = async (): Promise => { - if (storage && typeof storage.getRecentSessions === 'function') { - const sessions = await storage.getRecentSessions(20); - return sessions.map((s) => ({ - id: s.id, - agentName: s.agentName, - cli: s.cli, - startedAt: new Date(s.startedAt).toISOString(), - endedAt: s.endedAt ? new Date(s.endedAt).toISOString() : undefined, - duration: formatDuration(s.startedAt, s.endedAt), - messageCount: s.messageCount, - summary: s.summary, - isActive: !s.endedAt, - closedBy: s.closedBy, - })); - } - return []; - }; - - const getAgentSummaries = async (): Promise => { - if (storage && typeof storage.getAllAgentSummaries === 'function') { - const summaries = await storage.getAllAgentSummaries(); - return summaries.map((s) => ({ - agentName: s.agentName, - lastUpdated: new Date(s.lastUpdated).toISOString(), - currentTask: s.currentTask, - completedTasks: s.completedTasks, - context: s.context, - })); - } - return []; - }; - - const getAllData = async () => { - const team = getTeamData(); - if (!team) return null; - - const agentsMap = new Map(); - const allMessages = await getMessages(team.agents); - - // Initialize agents from config. - team.agents.forEach((a) => { - agentsMap.set(a.name, { - name: a.name, - role: a.role, - cli: a.cli ?? 'Unknown', - messageCount: 0, - status: 'Idle', - lastSeen: a.lastSeen, - lastActive: a.lastActive, - needsAttention: false, - team: a.team, - }); - }); - - // Inject online human users (connected via dashboard WebSocket) into agentsMap. - for (const [username, state] of onlineUsers) { - const existing = agentsMap.get(username); - if (existing) { - existing.cli = 'dashboard'; - existing.status = 'online'; - existing.avatarUrl = state.info.avatarUrl || existing.avatarUrl; - } else { - agentsMap.set(username, { - name: username, - role: 'User', - cli: 'dashboard', - messageCount: 0, - status: 'online', - lastSeen: state.info.lastSeen, - lastActive: state.info.lastSeen, - needsAttention: false, - avatarUrl: state.info.avatarUrl, - }); - } - } - - // Inject remote users (connected via cloud dashboard) into agentsMap. - const remoteUsersPath = path.join(teamDir, 'remote-users.json'); - if (fs.existsSync(remoteUsersPath)) { - try { - const remoteData = JSON.parse(fs.readFileSync(remoteUsersPath, 'utf-8')) as { updatedAt?: number; users?: RemoteUserRecord[] }; - // Only include if file is fresh (within 60 seconds). - if (remoteData.updatedAt && Date.now() - remoteData.updatedAt <= 60 * 1000) { - for (const user of remoteData.users || []) { - // Don't override local users. - if (onlineUsers.has(user.name)) continue; - - const existing = agentsMap.get(user.name); - if (existing) { - existing.cli = 'dashboard'; - existing.status = 'online'; - if (user.avatarUrl) existing.avatarUrl = user.avatarUrl; - } else { - // Use stable timestamps from the user/file data, not new Date(). - const stableTimestamp = user.lastSeen || user.connectedAt || new Date(remoteData.updatedAt).toISOString(); - agentsMap.set(user.name, { - name: user.name, - role: 'User', - cli: 'dashboard', - messageCount: 0, - status: 'online', - lastSeen: stableTimestamp, - lastActive: stableTimestamp, - needsAttention: false, - avatarUrl: user.avatarUrl, - }); - } - } - } - } catch { - // Ignore parse errors for remote users file. - } - } - - // Update inbox counts if fallback mode; if storage, count messages addressed to agent. - if (storage) { - for (const msg of allMessages) { - const agent = agentsMap.get(msg.to); - if (agent) { - agent.messageCount = (agent.messageCount ?? 0) + 1; - } - } - } else { - // Sort by timestamp. - allMessages.sort((a, b) => { - return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(); - }); - } - - // Derive status from messages sent BY agents. - allMessages.forEach((m) => { - const agent = agentsMap.get(m.from); - if (agent) { - agent.lastActive = m.timestamp; - // Don't overwrite lastSeen - it comes from registry (heartbeat/connection tracking). - if (m.content.startsWith('STATUS:')) { - agent.status = m.content.substring(7).trim(); - } - } - }); - - // Needs-attention ownership moved to cloud; dashboard now consumes pass-through data. - try { - const response = await fetchCloudNeedsAttention({ - request: { workspaceId: defaultWorkspaceId }, - }); - if (response.ok) { - const payload = await response.json() as Parameters[0]; - const needsAttentionAgents = parseNeedsAttentionAgents(payload); - needsAttentionAgents.forEach((agentName) => { - const agent = agentsMap.get(agentName); - if (agent) { - agent.needsAttention = true; - } - }); - } - } catch (err) { - debug?.(`[dashboard] cloud needs-attention proxy failed: ${(err as Error).message}`); - } - - // Read processing state from daemon. - const processingStatePath = path.join(teamDir, 'processing-state.json'); - if (fs.existsSync(processingStatePath)) { - try { - const processingData = JSON.parse(fs.readFileSync(processingStatePath, 'utf-8')) as { processingAgents?: Record }; - const processingAgents = processingData.processingAgents || {}; - for (const [agentName, state] of Object.entries(processingAgents)) { - const agent = agentsMap.get(agentName); - if (agent && state && typeof state === 'object') { - agent.isProcessing = true; - agent.processingStartedAt = state.startedAt; - } - } - } catch { - // Ignore errors reading processing state - it's optional. - } - } - - // Mark spawned agents with isSpawned flag, team, model, and cwd. - if (spawnReader) { - const activeWorkers = spawnReader.getActiveWorkers(); - for (const worker of activeWorkers) { - const agent = agentsMap.get(worker.name); - if (agent) { - agent.isSpawned = true; - if (worker.team) { - agent.team = worker.team; - } - const workerCwd = agentCwdMap.get(worker.name) || worker.cwd; - if (workerCwd) { - agent.cwd = workerCwd; - } - if (worker.cli) { - const modelMatch = worker.cli.match(/--model[=\s]+(\S+)/); - if (modelMatch) { - agent.model = modelMatch[1]; - } - } - } - } - } - - // Inject cwd from agentCwdMap for agents not in spawner's active workers. - for (const [name, cwd] of agentCwdMap) { - const agent = agentsMap.get(name); - if (agent && !agent.cwd) { - agent.cwd = cwd; - } - } - - // Also check workers.json for externally-spawned workers. - const workersJsonPath = path.join(teamDir, 'workers.json'); - if (fs.existsSync(workersJsonPath)) { - try { - const workersData = JSON.parse(fs.readFileSync(workersJsonPath, 'utf-8')) as { workers?: Array<{ name: string; logFile?: string }> }; - for (const worker of workersData.workers || []) { - const agent = agentsMap.get(worker.name); - if (agent && !agent.isSpawned && worker.logFile && fs.existsSync(worker.logFile)) { - agent.isSpawned = true; - } - } - } catch { - // Ignore errors reading workers.json. - } - } - - // Mark relay-protocol spawned agents by log-file presence. - if (spawnReader) { - for (const [name, agent] of agentsMap) { - if (agent.isSpawned) continue; - if (onlineUsers.has(name) || name === 'Dashboard') continue; - const logPath = path.join(teamDir, `${name}.log`); - if (fs.existsSync(logPath)) { - agent.isSpawned = true; - } - } - } - - // Set team from teams.json for agents that don't have a team yet. - const teamsConfig = loadTeamsConfig(projectRoot || dataDir); - if (teamsConfig) { - for (const teamAgent of teamsConfig.agents) { - const agent = agentsMap.get(teamAgent.name); - if (agent && !agent.team) { - agent.team = teamsConfig.team; - } - } - } - - // Fetch sessions and summaries in parallel. - const [sessions, summaries] = await Promise.all([ - getRecentSessions(), - getAgentSummaries(), - ]); - - // Filter and separate agents from human users. - const now = Date.now(); - const OFFLINE_THRESHOLD_MS = 30 * 1000; - - const validEntries = Array.from(agentsMap.values()) - .filter((agent) => { - if (agent.name === 'Dashboard') return false; - if (agent.name.startsWith('__')) return false; - if (agent.name === 'Dashboard') return false; - if (!agent.cli || agent.cli === 'Unknown') return false; - if (!agent.lastSeen) return false; - const lastSeenTime = new Date(agent.lastSeen).getTime(); - if (now - lastSeenTime > OFFLINE_THRESHOLD_MS) return false; - return true; - }); - - const filteredAgents = validEntries - .filter((agent) => agent.cli !== 'dashboard') - .map((agent) => ({ - ...agent, - isHuman: false as const, - })) - .sort((a, b) => a.name.localeCompare(b.name)); - - const humanUsers = validEntries - .filter((agent) => agent.cli === 'dashboard') - .map((agent) => ({ - ...agent, - isHuman: true as const, - })) - .sort((a, b) => a.name.localeCompare(b.name)); - - return { - agents: filteredAgents, - users: humanUsers, - messages: allMessages, - activity: allMessages, - sessions, - summaries, - }; - }; - - const getBridgeData = async (): Promise => { - const bridgeStatePath = path.join(dataDir, 'bridge-state.json'); - if (fs.existsSync(bridgeStatePath)) { - try { - const bridgeState = JSON.parse(fs.readFileSync(bridgeStatePath, 'utf-8')) as BridgeData; - - // Enrich each project with actual agent data from their team directories. - if (bridgeState.projects && Array.isArray(bridgeState.projects)) { - for (const project of bridgeState.projects) { - if (project.path) { - const projectHash = crypto.createHash('sha256').update(project.path).digest('hex').slice(0, 12); - const projectDataDir = path.join(path.dirname(dataDir), projectHash); - const projectTeamDir = path.join(projectDataDir, 'team'); - const agentsPath = path.join(projectTeamDir, 'agents.json'); - - if (fs.existsSync(agentsPath)) { - try { - const agentsData = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')) as { agents?: Array<{ name: string; cli?: string; lastSeen?: string }> }; - if (agentsData.agents && Array.isArray(agentsData.agents)) { - // Filter to only show online agents (seen within 30 seconds - aligns with heartbeat timeout). - const thirtySecondsAgo = Date.now() - 30 * 1000; - project.agents = agentsData.agents - .filter((a) => { - if (!a.lastSeen) return false; - return new Date(a.lastSeen).getTime() > thirtySecondsAgo; - }) - .map((a) => ({ - name: a.name, - status: 'active', - cli: a.cli, - lastSeen: a.lastSeen, - })); - - // Update lead status based on actual agents. - if (project.lead) { - const leadAgent = project.agents.find((a) => - a.name.toLowerCase() === project.lead!.name.toLowerCase(), - ); - project.lead.connected = !!leadAgent; - } - } - } catch (e) { - console.error(`Failed to read agents for ${project.path}:`, e); - } - } - } - } - } - - return bridgeState; - } catch { - return { projects: [], messages: [], connected: false }; - } - } - return { projects: [], messages: [], connected: false }; - }; - - return { - getAllData, - getBridgeData, - isInternalAgent, - remapAgentName, - buildThreadSummaryMap, - formatDuration, - }; -} diff --git a/packages/dashboard-server/src/lib/server-state.ts b/packages/dashboard-server/src/lib/server-state.ts deleted file mode 100644 index 1cf8702..0000000 --- a/packages/dashboard-server/src/lib/server-state.ts +++ /dev/null @@ -1,278 +0,0 @@ -import fs from 'fs'; -import type { WebSocketServer, WebSocket } from 'ws'; -import type { StorageAdapter } from '@agent-relay/storage/adapter'; -import type { RelayAdapter } from '@agent-relay/sdk'; -import { MessageBuffer } from '../messageBuffer.js'; - -export interface ResolveWorkspaceRequest { - query?: Record; - body?: Record; - headers?: Record; -} - -export interface SpawnWorkerLike { - name: string; - cli: string; - task: string; - team?: string; - spawnerName?: string; - spawnedAt: number; - pid?: number; -} - -export interface SpawnReaderLike { - getActiveWorkers(): SpawnWorkerLike[]; - hasWorker(name: string): boolean; - getWorkerOutput(name: string, limit?: number): string[] | undefined; - getWorkerRawOutput(name: string): string | undefined; - sendWorkerInput(name: string, data: string): Promise; -} - -type BrokerShimSpawnRequest = { - name: string; - cli: string; - task?: string; - team?: string; - cwd?: string; - interactive?: boolean; - shadowMode?: string; - shadowAgent?: string; - shadowOf?: string; - shadowTriggers?: string; - shadowSpeakOn?: string; - spawnerName?: string; - userId?: string; - includeWorkflowConventions?: boolean; -}; - -export type BrokerRelayClientShim = { - state: 'READY'; - connect: () => Promise; - disconnect: () => void; - sendMessage: ( - to: string, - body: string, - _kind?: string, - data?: unknown, - thread?: string - ) => boolean; - joinChannel: (channel: string, displayName?: string) => boolean; - leaveChannel: (channel: string, reason?: string) => boolean; - sendChannelMessage: ( - channel: string, - body: string, - options?: { thread?: string; mentions?: string[]; attachments?: unknown[]; data?: Record } - ) => boolean; - adminJoinChannel: (channel: string, member: string) => boolean; - adminRemoveMember: (channel: string, member: string) => boolean; - spawn: (req: BrokerShimSpawnRequest) => Promise; - release: (name: string) => Promise; -}; - -export interface ServerState< - TPresenceState = unknown, - TAttachment = unknown, - TDecision = unknown, - TTask = unknown, -> { - config: { - dataDir: string; - teamDir: string; - projectRoot?: string; - verbose: boolean; - defaultWorkspaceId?: string; - }; - refs: { - storage?: StorageAdapter; - relayAdapter: RelayAdapter; - spawnReader: SpawnReaderLike; - wss: WebSocketServer; - wssBridge: WebSocketServer; - wssLogs: WebSocketServer; - wssPresence: WebSocketServer; - }; - mainClientAlive: WeakMap; - bridgeClientAlive: WeakMap; - presenceHealth: WeakMap; - mainMessageBuffer: MessageBuffer; - agentLogBuffers: Map; - logSubscriptions: Map>; - fileWatchers: Map; - fileLastSize: Map; - onlineUsers: Map; - initializingClients: WeakSet; - attachmentRegistry: Map; - agentCwdMap: Map; - decisions: Map; - tasks: Map; - brokerClientShimCache: Map; - getAgentLogBuffer: (agentName: string) => MessageBuffer; - getBrokerClientShim: (senderName: string) => BrokerRelayClientShim; - resolveWorkspaceId: (req: ResolveWorkspaceRequest) => string | undefined; -} - -export interface CreateServerStateOptions { - dataDir: string; - teamDir: string; - projectRoot?: string; - verbose: boolean; - defaultWorkspaceId?: string; - storage?: StorageAdapter; - relayAdapter: RelayAdapter; - spawnReader: SpawnReaderLike; - wss: WebSocketServer; - wssBridge: WebSocketServer; - wssLogs: WebSocketServer; - wssPresence: WebSocketServer; -} - -const asString = (value: unknown): string | undefined => { - if (typeof value === 'string' && value.length > 0) return value; - if (Array.isArray(value)) { - for (const item of value) { - if (typeof item === 'string' && item.length > 0) return item; - } - } - return undefined; -}; - -const getWorkspaceHeader = (headers: Record | undefined): string | undefined => { - if (!headers) return undefined; - const direct = headers['x-workspace-id']; - if (typeof direct === 'string' && direct.length > 0) return direct; - for (const [key, value] of Object.entries(headers)) { - if (key.toLowerCase() === 'x-workspace-id' && typeof value === 'string' && value.length > 0) { - return value; - } - } - return undefined; -}; - -export function createServerState(options: CreateServerStateOptions): ServerState { - const { - dataDir, - teamDir, - projectRoot, - verbose, - defaultWorkspaceId, - storage, - relayAdapter, - spawnReader, - wss, - wssBridge, - wssLogs, - wssPresence, - } = options; - - const brokerClientShimCache = new Map(); - - const getBrokerClientShim = (senderName: string): BrokerRelayClientShim => { - let shim = brokerClientShimCache.get(senderName); - if (!shim) { - const sendViaBroker = (to: string, body: string, thread?: string, data?: Record): boolean => { - void relayAdapter.sendMessage({ - to, - text: body, - from: senderName, - threadId: thread, - data, - }).catch((err) => { - console.error(`[dashboard] Failed to send broker message from ${senderName} to ${to}:`, err); - }); - return true; - }; - - shim = { - state: 'READY', - connect: async () => {}, - disconnect: () => {}, - sendMessage: (to: string, body: string, _kind?: string, data?: unknown, thread?: string) => { - const typedData = (data && typeof data === 'object') ? data as Record : undefined; - return sendViaBroker(to, body, thread, typedData); - }, - joinChannel: (_channel: string, _displayName?: string) => true, - leaveChannel: (_channel: string, _reason?: string) => true, - sendChannelMessage: (channel: string, body: string, options?: { thread?: string; mentions?: string[]; attachments?: unknown[]; data?: Record }) => { - const channelData: Record = { - ...(options?.data || {}), - _isChannelMessage: true, - }; - if (options?.mentions) channelData.mentions = options.mentions; - if (options?.attachments) channelData.attachments = options.attachments; - return sendViaBroker(channel, body, options?.thread, channelData); - }, - adminJoinChannel: (_channel: string, _member: string) => true, - adminRemoveMember: (_channel: string, _member: string) => true, - spawn: async (req: BrokerShimSpawnRequest) => { - return relayAdapter.spawn({ - name: req.name, - cli: req.cli, - task: req.task, - team: req.team, - cwd: req.cwd, - interactive: req.interactive, - shadowMode: req.shadowMode, - shadowOf: req.shadowOf, - spawnerName: req.spawnerName, - userId: req.userId, - includeWorkflowConventions: req.includeWorkflowConventions, - }); - }, - release: async (name: string) => relayAdapter.release(name), - }; - brokerClientShimCache.set(senderName, shim); - } - return shim; - }; - - const state: ServerState = { - config: { - dataDir, - teamDir, - projectRoot, - verbose, - defaultWorkspaceId, - }, - refs: { - storage, - relayAdapter, - spawnReader, - wss, - wssBridge, - wssLogs, - wssPresence, - }, - mainClientAlive: new WeakMap(), - bridgeClientAlive: new WeakMap(), - presenceHealth: new WeakMap(), - mainMessageBuffer: new MessageBuffer(500), - agentLogBuffers: new Map(), - logSubscriptions: new Map>(), - fileWatchers: new Map(), - fileLastSize: new Map(), - onlineUsers: new Map(), - initializingClients: new WeakSet(), - attachmentRegistry: new Map(), - agentCwdMap: new Map(), - decisions: new Map(), - tasks: new Map(), - brokerClientShimCache, - getAgentLogBuffer: (agentName: string): MessageBuffer => { - let buffer = state.agentLogBuffers.get(agentName); - if (!buffer) { - buffer = new MessageBuffer(200); - state.agentLogBuffers.set(agentName, buffer); - } - return buffer; - }, - getBrokerClientShim, - resolveWorkspaceId: (req: ResolveWorkspaceRequest): string | undefined => { - const fromQuery = asString(req.query?.workspaceId); - const fromBody = asString(req.body?.workspaceId); - const fromHeader = getWorkspaceHeader(req.headers); - return fromQuery || fromBody || fromHeader || defaultWorkspaceId; - }, - }; - - return state; -} diff --git a/packages/dashboard-server/src/lib/websocket-runtime.ts b/packages/dashboard-server/src/lib/websocket-runtime.ts deleted file mode 100644 index da204b4..0000000 --- a/packages/dashboard-server/src/lib/websocket-runtime.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { Server } from 'http'; -import type { WebSocket, WebSocketServer } from 'ws'; - -export interface WebSocketRuntimeDeps { - server: Server; - wss: WebSocketServer; - wssBridge: WebSocketServer; - wssLogs: WebSocketServer; - wssPresence: WebSocketServer; - mainClientAlive: WeakMap; - bridgeClientAlive: WeakMap; - debug: (message: string) => void; -} - -/** - * Configure ping/pong health checks, HTTP upgrade routing, and server-level WS error handlers. - */ -export function setupWebSocketRuntime(deps: WebSocketRuntimeDeps): void { - const { - server, - wss, - wssBridge, - wssLogs, - wssPresence, - mainClientAlive, - bridgeClientAlive, - debug, - } = deps; - - const pingIntervalMs = 15000; - - const mainPingInterval = setInterval(() => { - wss.clients.forEach((ws) => { - if (mainClientAlive.get(ws) === false) { - debug('[dashboard] Main WebSocket client unresponsive, closing gracefully'); - ws.close(1000, 'unresponsive'); - return; - } - mainClientAlive.set(ws, false); - ws.ping(); - }); - }, pingIntervalMs); - - const bridgePingInterval = setInterval(() => { - wssBridge.clients.forEach((ws) => { - if (bridgeClientAlive.get(ws) === false) { - debug('[dashboard] Bridge WebSocket client unresponsive, closing gracefully'); - ws.close(1000, 'unresponsive'); - return; - } - bridgeClientAlive.set(ws, false); - ws.ping(); - }); - }, pingIntervalMs); - - wss.on('close', () => { - clearInterval(mainPingInterval); - }); - - wssBridge.on('close', () => { - clearInterval(bridgePingInterval); - }); - - server.on('upgrade', (request, socket, head) => { - const pathname = new URL(request.url || '', `http://${request.headers.host ?? 'localhost'}`).pathname; - - if (pathname === '/ws') { - wss.handleUpgrade(request, socket, head, (ws) => { - wss.emit('connection', ws, request); - }); - return; - } - - if (pathname === '/ws/bridge') { - wssBridge.handleUpgrade(request, socket, head, (ws) => { - wssBridge.emit('connection', ws, request); - }); - return; - } - - if (pathname === '/ws/logs' || pathname.startsWith('/ws/logs/')) { - wssLogs.handleUpgrade(request, socket, head, (ws) => { - wssLogs.emit('connection', ws, request); - }); - return; - } - - if (pathname === '/ws/presence') { - wssPresence.handleUpgrade(request, socket, head, (ws) => { - wssPresence.emit('connection', ws, request); - }); - return; - } - - socket.destroy(); - }); - - wss.on('error', (err) => { - console.error('[dashboard] WebSocket server error:', err); - }); - wssBridge.on('error', (err) => { - console.error('[dashboard] Bridge WebSocket server error:', err); - }); - wssLogs.on('error', (err) => { - console.error('[dashboard] Logs WebSocket server error:', err); - }); - wssPresence.on('error', (err) => { - console.error('[dashboard] Presence WebSocket server error:', err); - }); -} diff --git a/packages/dashboard-server/src/routes/auth.ts b/packages/dashboard-server/src/routes/auth.ts deleted file mode 100644 index 5732d0c..0000000 --- a/packages/dashboard-server/src/routes/auth.ts +++ /dev/null @@ -1,346 +0,0 @@ -import crypto from 'crypto'; -import fs from 'fs'; -import path from 'path'; -import type { Application, RequestHandler } from 'express'; - -type CliAuthSession = { - id: string; - status: 'starting' | 'waiting_auth' | 'success' | 'error'; - authUrl?: string; - error?: string; - token?: string; - refreshToken?: string; - tokenExpiresAt?: Date; -}; - -type StartCliAuthResult = { - id: string; - status: string; - authUrl?: string; -}; - -type SubmitAuthCodeResult = { - success: boolean; - error?: string; - needsRestart?: boolean; -}; - -type CompleteAuthResult = { - success: boolean; - error?: string; -}; - -type SupportedProvider = { - id: string; - displayName?: string; - command?: string; -}; - -export interface AuthRouteDeps { - startCLIAuth: (provider: string, options?: { userId?: string }) => Promise; - getAuthSession: (sessionId: string) => CliAuthSession | null; - cancelAuthSession: (sessionId: string) => boolean; - submitAuthCode: (sessionId: string, code: string) => Promise; - completeAuthSession: (sessionId: string) => Promise; - getSupportedProviders: () => SupportedProvider[]; -} - -/** - * CLI auth and credential-management routes. - */ -export function registerAuthRoutes(app: Application, deps: AuthRouteDeps): void { - const { - startCLIAuth, - getAuthSession, - cancelAuthSession, - submitAuthCode, - completeAuthSession, - getSupportedProviders, - } = deps; - - const validateWorkspaceToken: RequestHandler = (req, res, next) => { - // Skip auth validation in local mode (no WORKSPACE_TOKEN set). - const expectedToken = process.env.WORKSPACE_TOKEN; - if (!expectedToken) { - return next(); - } - - const authHeader = req.headers.authorization; - const token = authHeader?.startsWith('Bearer ') - ? authHeader.substring(7) - : null; - - if (!token) { - console.warn('[dashboard] Unauthorized CLI auth request - missing workspace token'); - return res.status(401).json({ error: 'Unauthorized - invalid workspace token' }); - } - - const tokenBuffer = Buffer.from(token); - const expectedBuffer = Buffer.from(expectedToken); - const isValidToken = tokenBuffer.length === expectedBuffer.length && - crypto.timingSafeEqual(tokenBuffer, expectedBuffer); - - if (!isValidToken) { - console.warn('[dashboard] Unauthorized CLI auth request - invalid workspace token'); - return res.status(401).json({ error: 'Unauthorized - invalid workspace token' }); - } - - next(); - }; - - // Apply workspace-token validation to all CLI auth endpoints. - app.use('/auth/cli', validateWorkspaceToken); - // Apply workspace-token validation to credential mutation endpoints. - app.use('/api/credentials', validateWorkspaceToken); - - // POST /auth/cli/:provider/start - Start CLI auth flow. - app.post('/auth/cli/:provider/start', async (req, res) => { - const { provider } = req.params; - const { userId } = req.body || {}; - try { - const session = await startCLIAuth(provider, { userId }); - return res.json({ - sessionId: session.id, - status: session.status, - authUrl: session.authUrl, - }); - } catch (err) { - return res.status(400).json({ - error: err instanceof Error ? err.message : 'Failed to start CLI auth', - }); - } - }); - - // GET /auth/cli/:provider/status/:sessionId - Get auth session status. - app.get('/auth/cli/:provider/status/:sessionId', (req, res) => { - const { sessionId } = req.params; - const session = getAuthSession(sessionId); - if (!session) { - return res.status(404).json({ error: 'Session not found' }); - } - return res.json({ - status: session.status, - authUrl: session.authUrl, - error: session.error, - }); - }); - - // GET /auth/cli/:provider/creds/:sessionId - Get credentials from completed auth. - app.get('/auth/cli/:provider/creds/:sessionId', (req, res) => { - const { sessionId } = req.params; - const session = getAuthSession(sessionId); - if (!session) { - return res.status(404).json({ error: 'Session not found' }); - } - if (session.status !== 'success') { - return res.status(400).json({ error: 'Auth not complete', status: session.status }); - } - return res.json({ - token: session.token, - refreshToken: session.refreshToken, - expiresAt: session.tokenExpiresAt?.toISOString(), - }); - }); - - // POST /auth/cli/:provider/cancel/:sessionId - Cancel auth session. - app.post('/auth/cli/:provider/cancel/:sessionId', (req, res) => { - const { sessionId } = req.params; - const cancelled = cancelAuthSession(sessionId); - if (!cancelled) { - return res.status(404).json({ error: 'Session not found' }); - } - return res.json({ success: true }); - }); - - // POST /auth/cli/:provider/code/:sessionId - Submit auth code to PTY. - app.post('/auth/cli/:provider/code/:sessionId', async (req, res) => { - const { provider, sessionId } = req.params; - const { code } = req.body; - - console.log('[cli-auth] Auth code submission received', { provider, sessionId, codeLength: code?.length }); - - if (!code || typeof code !== 'string') { - return res.status(400).json({ error: 'Auth code is required' }); - } - - try { - const result = await submitAuthCode(sessionId, code); - console.log('[cli-auth] Auth code submission result', { provider, sessionId, result }); - - if (!result.success) { - return res.status(400).json({ - error: result.error || 'Session not found or process not running', - needsRestart: result.needsRestart ?? true, - }); - } - - // Poll briefly for auth completion after code submission. - let sessionStatus = 'waiting_auth'; - for (let i = 0; i < 10; i++) { - await new Promise((resolve) => setTimeout(resolve, 500)); - const session = getAuthSession(sessionId); - if (session?.status === 'success') { - sessionStatus = 'success'; - console.log('[cli-auth] Credentials found after code submission', { provider, sessionId, attempt: i + 1 }); - break; - } - if (session?.status === 'error') { - sessionStatus = 'error'; - break; - } - } - - return res.json({ - success: true, - message: 'Auth code submitted', - status: sessionStatus, - }); - } catch (err) { - console.error('[cli-auth] Auth code submission error', { provider, sessionId, error: String(err) }); - return res.status(500).json({ - error: 'Internal error submitting auth code. Please try again.', - needsRestart: true, - }); - } - }); - - // POST /auth/cli/:provider/complete/:sessionId - Complete auth. - app.post('/auth/cli/:provider/complete/:sessionId', async (req, res) => { - const { sessionId } = req.params; - const { authCode } = req.body || {}; - - if (authCode && typeof authCode === 'string') { - let code = authCode; - if (authCode.startsWith('http')) { - try { - const url = new URL(authCode); - const codeParam = url.searchParams.get('code'); - if (codeParam) { - code = codeParam; - } - } catch { - // Not a valid URL, use as-is. - } - } - - const submitResult = await submitAuthCode(sessionId, code); - if (!submitResult.success) { - return res.status(400).json({ - error: submitResult.error, - needsRestart: submitResult.needsRestart, - }); - } - - await new Promise((resolve) => setTimeout(resolve, 2000)); - } - - const result = await completeAuthSession(sessionId); - if (!result.success) { - return res.status(400).json({ error: result.error }); - } - - return res.json({ success: true, message: 'Authentication complete' }); - }); - - // GET /auth/cli/providers - List supported providers. - app.get('/auth/cli/providers', (_req, res) => { - return res.json({ providers: getSupportedProviders() }); - }); - - // GET /auth/cli/openai/check - Check if OpenAI/Codex is authenticated. - app.get('/auth/cli/openai/check', async (req, res) => { - try { - const userId = typeof req.query.userId === 'string' ? req.query.userId : undefined; - if (userId && !/^[a-zA-Z0-9_-]+$/.test(userId)) { - return res.status(400).json({ error: 'Invalid userId format' }); - } - - let credPath: string; - if (userId) { - const dataDir = process.env.AGENT_RELAY_DATA_DIR || '/data'; - credPath = path.join(dataDir, 'users', userId, '.codex', 'auth.json'); - } else { - const homedir = process.env.HOME || '/home/workspace'; - credPath = path.join(homedir, '.codex', 'auth.json'); - } - - if (!fs.existsSync(credPath)) { - return res.json({ authenticated: false }); - } - - const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8')); - const hasToken = !!( - creds.access_token || - creds.token || - creds.api_key || - creds.OPENAI_API_KEY || - creds.tokens?.access_token || - creds.tokens?.refresh_token - ); - - return res.json({ authenticated: hasToken }); - } catch { - return res.json({ authenticated: false }); - } - }); - - // POST /api/credentials/apikey - Persist API key credential. - app.post('/api/credentials/apikey', async (req, res) => { - const { userId, provider, apiKey } = req.body; - - if (!userId || typeof userId !== 'string') { - return res.status(400).json({ error: 'userId is required' }); - } - if (!provider || typeof provider !== 'string') { - return res.status(400).json({ error: 'provider is required' }); - } - if (!apiKey || typeof apiKey !== 'string') { - return res.status(400).json({ error: 'apiKey is required' }); - } - - try { - const { getUserDirectoryService } = await import('@agent-relay/user-directory'); - const userDirService = getUserDirectoryService(); - const credPath = userDirService.writeApiKeyCredential(userId, provider, apiKey); - - console.log(`[credentials] Wrote ${provider} API key for user ${userId} to ${credPath}`); - - return res.json({ - success: true, - message: `${provider} API key saved`, - path: credPath, - }); - } catch (err) { - console.error(`[credentials] Failed to write ${provider} API key for user ${userId}:`, err); - return res.status(500).json({ error: 'Failed to write credential file' }); - } - }); - - // DELETE /api/credentials/apikey - Delete provider credential files. - app.delete('/api/credentials/apikey', async (req, res) => { - const { userId, provider } = req.body; - - if (!userId || typeof userId !== 'string') { - return res.status(400).json({ error: 'userId is required' }); - } - if (!provider || typeof provider !== 'string') { - return res.status(400).json({ error: 'provider is required' }); - } - - try { - const { getUserDirectoryService } = await import('@agent-relay/user-directory'); - const userDirService = getUserDirectoryService(); - const deletedPaths = userDirService.deleteProviderCredentials(userId, provider); - - console.log(`[credentials] Deleted ${provider} credentials for user ${userId}:`, deletedPaths); - - return res.json({ - success: true, - deletedPaths, - }); - } catch (err) { - console.error(`[credentials] Failed to delete ${provider} credentials for user ${userId}:`, err); - return res.status(500).json({ error: 'Failed to delete credential files' }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/channels-integrated.ts b/packages/dashboard-server/src/routes/channels-integrated.ts deleted file mode 100644 index f18fc14..0000000 --- a/packages/dashboard-server/src/routes/channels-integrated.ts +++ /dev/null @@ -1,821 +0,0 @@ -import express, { type Application } from 'express'; -import fs from 'fs'; -import path from 'path'; -import crypto from 'crypto'; -import type { StorageAdapter } from '@agent-relay/storage/adapter'; -import type { ChannelRecord } from '../lib/channel-state.js'; - -interface Attachment { - id: string; - filename: string; - mimeType: string; - size: number; - url: string; - filePath?: string; - width?: number; - height?: number; - data?: string; -} - -interface ThreadSummary { - threadId: string; - replyCount: number; - participants: string[]; - lastReplyAt?: number; -} - -interface RelayClientLike { - state: string; - joinChannel: (channel: string, displayName?: string) => boolean; - sendChannelMessage: ( - channel: string, - body: string, - options?: { thread?: string; data?: Record; attachments?: unknown[] } - ) => boolean; -} - -interface UserBridgeLike { - getUserChannels(username: string): string[]; - getRegisteredUsers(): string[]; - isUserRegistered(username: string): boolean; - joinChannel(username: string, channel: string): Promise; - leaveChannel(username: string, channel: string): Promise; - adminJoinChannel(channel: string, member: string): Promise; - adminRemoveMember(channel: string, member: string): Promise; - sendChannelMessage( - username: string, - channel: string, - body: string, - options?: { thread?: string; data?: Record; attachments?: unknown[] } - ): Promise; - sendDirectMessage(from: string, to: string, body: string, options?: { thread?: string }): Promise; -} - -interface SpawnReaderLike { - getActiveWorkers(): Array<{ name: string }>; -} - -interface PresenceLike { - info: { - avatarUrl?: string; - }; -} - -export interface ChannelsIntegratedRouteDeps { - storage?: StorageAdapter; - teamDir: string; - attachmentRegistry: Map; - onlineUsers: Map; - userBridge?: UserBridgeLike; - spawnReader?: SpawnReaderLike; - resolveWorkspaceId: (req: { - query?: Record; - body?: Record; - headers?: Record; - }) => string | undefined; - loadChannelRecords: (workspaceId?: string) => Promise>; - persistChannelMembershipEvent: ( - channel: string, - member: string, - action: 'join' | 'leave' | 'invite', - options?: { invitedBy?: string; workspaceId?: string } - ) => Promise; - getRelayClient: (senderName?: string, entityType?: 'agent' | 'user') => Promise; - buildThreadSummaryMap: (messages: any[]) => Map; - isInternalAgent: (name: string) => boolean; - getAllData: () => Promise; -} - -/** - * Integrated channel + DM routes (separate from proxy-mode routes/channels.ts). - */ -export function registerChannelsIntegratedRoutes(app: Application, deps: ChannelsIntegratedRouteDeps): void { - const { - storage, - teamDir, - attachmentRegistry, - onlineUsers, - userBridge, - spawnReader, - resolveWorkspaceId, - loadChannelRecords, - persistChannelMembershipEvent, - getRelayClient, - buildThreadSummaryMap, - isInternalAgent, - getAllData, - } = deps; - - app.get('/api/data', (req, res) => { - getAllData().then((data) => { - const safeData = data ?? { agents: [], users: [], messages: [], activity: [], sessions: [], summaries: [] }; - res.json(safeData); - }).catch((err) => { - console.error('Failed to fetch dashboard data', err); - res.status(500).json({ error: 'Failed to load data' }); - }); - }); - - app.get('/api/channels', async (req, res) => { - const username = req.query.username as string | undefined; - const workspaceId = resolveWorkspaceId(req); - - if (!storage) { - if (!username) { - return res.status(400).json({ error: 'username query param required' }); - } - const channels = userBridge?.getUserChannels(username) ?? []; - return res.json({ - channels: channels.map((id: string) => ({ - id, - name: id.startsWith('#') ? id.slice(1) : id, - visibility: 'public', - status: 'active', - createdAt: new Date().toISOString(), - createdBy: username, - memberCount: 0, - unreadCount: 0, - hasMentions: false, - isDm: id.startsWith('dm:'), - })), - archivedChannels: [], - }); - } - - try { - const channelMap = await loadChannelRecords(workspaceId); - type ChannelResponse = { - id: string; - name: string; - description?: string; - visibility: string; - status: string; - createdAt: string; - createdBy: string; - lastActivityAt?: string; - memberCount: number; - unreadCount: number; - hasMentions: boolean; - lastMessage?: { content: string; from: string; timestamp: string }; - isDm: boolean; - dmParticipants?: string[]; - }; - const activeChannels: ChannelResponse[] = []; - const archivedChannels: ChannelResponse[] = []; - - for (const record of channelMap.values()) { - const isMember = !username || record.members.has(username) || record.id === '#general'; - if (!isMember) { - continue; - } - - const channel = { - id: record.id, - name: record.id.startsWith('#') ? record.id.slice(1) : record.id, - description: record.description, - visibility: record.visibility, - status: record.status, - createdAt: record.createdAt ? new Date(record.createdAt).toISOString() : new Date(record.lastActivityAt || Date.now()).toISOString(), - createdBy: record.createdBy || '__system__', - lastActivityAt: record.lastActivityAt ? new Date(record.lastActivityAt).toISOString() : undefined, - memberCount: record.members.size, - unreadCount: 0, - hasMentions: false, - lastMessage: record.lastMessage, - isDm: record.id.startsWith('dm:'), - dmParticipants: record.dmParticipants, - }; - - if (record.status === 'archived') { - archivedChannels.push(channel); - } else { - activeChannels.push(channel); - } - } - - return res.json({ - channels: activeChannels, - archivedChannels, - }); - } catch (err) { - console.error('[channels] Failed to load channels', err); - return res.status(500).json({ error: 'Failed to load channels' }); - } - }); - - app.get('/api/channels/available-members', async (_req, res) => { - try { - const availableAgents: Array<{ id: string; displayName: string; entityType: 'agent'; status: string }> = []; - - if (spawnReader) { - const activeWorkers = spawnReader.getActiveWorkers(); - for (const worker of activeWorkers) { - availableAgents.push({ - id: worker.name, - displayName: worker.name, - entityType: 'agent', - status: 'online', - }); - } - } - - if (userBridge) { - const registeredUsers = userBridge.getRegisteredUsers(); - for (const username of registeredUsers) { - if (!availableAgents.some((a) => a.id === username) && username !== 'Dashboard') { - availableAgents.push({ - id: username, - displayName: username, - entityType: 'agent', - status: 'online', - }); - } - } - } - - return res.json({ - success: true, - members: [], - agents: availableAgents, - }); - } catch (err) { - console.error('[channels] Failed to get available members:', err); - return res.status(500).json({ error: 'Failed to get available members' }); - } - }); - - app.post('/api/channels', express.json(), async (req, res) => { - const { name, description, isPrivate, invites } = req.body as { - name: string; - description?: string; - isPrivate?: boolean; - invites?: string; - username?: string; - }; - const workspaceId = resolveWorkspaceId(req); - const username = (req.query.username as string) || req.body.username || 'Dashboard'; - - if (!name) { - return res.status(400).json({ error: 'name is required' }); - } - - const channelId = name.startsWith('#') ? name : `#${name}`; - - try { - await userBridge?.joinChannel(username, channelId); - await persistChannelMembershipEvent(channelId, username, 'join', { workspaceId }); - - if (invites) { - const inviteList = invites.split(',').map((s) => s.trim()).filter(Boolean); - for (const invitee of inviteList) { - await userBridge?.joinChannel(invitee, channelId); - await persistChannelMembershipEvent(channelId, invitee, 'invite', { invitedBy: username, workspaceId }); - } - } - - if (storage) { - await storage.saveMessage({ - id: `channel-create-${crypto.randomUUID()}`, - ts: Date.now(), - from: '__system__', - to: channelId, - topic: undefined, - kind: 'state', - body: `Channel created by ${username}`, - data: { - _channelCreate: { - createdBy: username, - description, - isPrivate: isPrivate ?? false, - }, - ...(workspaceId ? { _workspaceId: workspaceId } : {}), - }, - status: 'read', - is_urgent: false, - is_broadcast: true, - }); - } - - return res.json({ - success: true, - channel: { - id: channelId, - name: name.startsWith('#') ? name.slice(1) : name, - description, - visibility: isPrivate ? 'private' : 'public', - status: 'active', - createdAt: new Date().toISOString(), - createdBy: username, - memberCount: 1, - }, - }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to create channel'; - console.error('[channels] Failed to create channel:', err); - return res.status(500).json({ error: message }); - } - }); - - app.post('/api/channels/invite', express.json(), async (req, res) => { - const { channel, invites, invitedBy } = req.body as { - channel: string; - invites: string | string[] | Array<{ id: string; type?: 'user' | 'agent' }>; - invitedBy?: string; - }; - const workspaceId = resolveWorkspaceId(req); - - if (!channel || !invites) { - return res.status(400).json({ error: 'channel and invites are required' }); - } - - const channelId = channel.startsWith('dm:') - ? channel - : (channel.startsWith('#') ? channel : `#${channel}`); - - type InviteItem = { id: string; type: 'user' | 'agent' }; - let inviteList: InviteItem[]; - - if (typeof invites === 'string') { - inviteList = invites.split(',').map((s: string) => s.trim()).filter(Boolean) - .map((id) => ({ id, type: 'agent' as const })); - } else if (Array.isArray(invites)) { - inviteList = invites.map((item) => { - if (typeof item === 'string') { - return { id: item, type: 'agent' as const }; - } - return { id: item.id, type: item.type || 'agent' }; - }); - } else { - return res.status(400).json({ error: 'invites must be a string or array' }); - } - - try { - const results: Array<{ id: string; type: string; success: boolean; reason?: string }> = []; - for (const invitee of inviteList) { - let success = false; - let reason: string | undefined; - if (userBridge?.isUserRegistered(invitee.id)) { - success = await userBridge.joinChannel(invitee.id, channelId); - if (!success) { - reason = 'join_failed'; - } - } else { - success = true; - reason = 'pending'; - } - - await persistChannelMembershipEvent(channelId, invitee.id, 'invite', { - invitedBy, - workspaceId, - }); - - results.push({ id: invitee.id, type: invitee.type, success, reason }); - } - - return res.json({ channel: channelId, invited: results }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to invite members'; - console.error('[channels] Failed to invite to channel:', err); - return res.status(500).json({ error: message }); - } - }); - - app.get('/api/channels/users', (_req, res) => { - const users = userBridge?.getRegisteredUsers() ?? []; - res.json({ users }); - }); - - app.post('/api/channels/join', express.json(), async (req, res) => { - const { username, channel } = req.body; - if (!username || !channel) { - return res.status(400).json({ error: 'username and channel required' }); - } - const workspaceId = resolveWorkspaceId(req); - const channelId = channel.startsWith('dm:') - ? channel - : (channel.startsWith('#') ? channel : `#${channel}`); - - let success = false; - const isLocalUser = userBridge?.isUserRegistered(username); - if (isLocalUser) { - success = await userBridge?.joinChannel(username, channelId) ?? false; - } - - if (!success) { - try { - const client = await getRelayClient(username); - if (client && client.state === 'READY') { - success = client.joinChannel(channelId, username); - } - } catch { - // Ignore fallback errors. - } - } - - if (success) { - await persistChannelMembershipEvent(channelId, username, 'join', { workspaceId }); - } - - return res.json({ success, channel: channelId }); - }); - - app.post('/api/channels/leave', express.json(), async (req, res) => { - const { username, channel } = req.body; - if (!username || !channel) { - return res.status(400).json({ error: 'username and channel required' }); - } - const workspaceId = resolveWorkspaceId(req); - try { - const success = await userBridge?.leaveChannel(username, channel); - if (success) { - await persistChannelMembershipEvent(channel, username, 'leave', { workspaceId }); - } - return res.json({ success, channel }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Leave failed'; - return res.status(500).json({ error: message }); - } - }); - - app.post('/api/channels/admin-join', express.json(), async (req, res) => { - const { channel, member } = req.body; - if (!channel || !member) { - return res.status(400).json({ error: 'channel and member required' }); - } - const workspaceId = resolveWorkspaceId(req); - try { - const success = await userBridge?.adminJoinChannel(channel, member); - if (success) { - await persistChannelMembershipEvent(channel, member, 'join', { workspaceId }); - } - - let warning: string | undefined; - const connectedAgentsPath = path.join(teamDir, 'connected-agents.json'); - try { - if (fs.existsSync(connectedAgentsPath)) { - const data = JSON.parse(fs.readFileSync(connectedAgentsPath, 'utf-8')); - const connectedAgents: string[] = data.agents || []; - const connectedUsers: string[] = data.users || []; - const allConnected = [...connectedAgents, ...connectedUsers]; - const isConnected = allConnected.some( - (name) => name.toLowerCase() === member.toLowerCase() - ); - if (!isConnected) { - warning = `Member "${member}" is not currently connected to the daemon. Messages sent to this channel will not be delivered until the agent connects.`; - } - } - } catch { - // Ignore parse errors. - } - - return res.json({ success, channel, member, warning }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Admin join failed'; - return res.status(500).json({ error: message }); - } - }); - - app.post('/api/channels/admin-remove', express.json(), async (req, res) => { - const { channel, member } = req.body; - if (!channel || !member) { - return res.status(400).json({ error: 'channel and member required' }); - } - const workspaceId = resolveWorkspaceId(req); - try { - const success = await userBridge?.adminRemoveMember(channel, member); - if (success) { - await persistChannelMembershipEvent(channel, member, 'leave', { workspaceId }); - } - return res.json({ success, channel, member }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Admin remove failed'; - return res.status(500).json({ error: message }); - } - }); - - app.get('/api/channels/:channel/members', async (req, res) => { - const channelId = req.params.channel.startsWith('#') ? req.params.channel : `#${req.params.channel}`; - const workspaceId = resolveWorkspaceId(req); - - try { - const channelMap = await loadChannelRecords(workspaceId); - const record = channelMap.get(channelId); - - const agentsPath = path.join(teamDir, 'agents.json'); - const onlineAgents: string[] = []; - const thirtySecondsAgo = Date.now() - 30 * 1000; - if (fs.existsSync(agentsPath)) { - try { - const data = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')); - for (const agent of (data.agents || [])) { - if (agent.lastSeen && new Date(agent.lastSeen).getTime() > thirtySecondsAgo) { - onlineAgents.push(agent.name); - } - } - } catch { - // Ignore parse errors. - } - } - - const connectedUsers = userBridge?.getRegisteredUsers() ?? []; - const memberSet = new Set(); - - if (record?.members) { - for (const member of record.members) { - memberSet.add(member); - } - } - - if (channelId === '#general') { - for (const agent of onlineAgents) { - memberSet.add(agent); - } - for (const user of connectedUsers) { - memberSet.add(user); - } - } - - const members = Array.from(memberSet).map((name) => { - const isOnlineAgent = onlineAgents.includes(name); - const isOnlineUser = connectedUsers.includes(name); - return { - id: name, - displayName: name, - entityType: isOnlineUser ? 'user' : 'agent', - role: 'member', - status: isOnlineAgent || isOnlineUser ? 'online' : 'offline', - joinedAt: new Date().toISOString(), - }; - }); - - return res.json({ members }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to get channel members'; - console.error('[channels] Failed to get channel members:', err); - return res.status(500).json({ error: message }); - } - }); - - app.get('/api/channels/:channel/messages', async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - const channelId = req.params.channel; - const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 200; - const beforeTs = req.query.before ? parseInt(req.query.before as string, 10) : undefined; - const workspaceId = resolveWorkspaceId(req); - - try { - const query: Record = { - to: channelId, - limit, - order: 'desc', - }; - if (beforeTs) { - query.sinceTs = beforeTs; - } - let messages = await storage.getMessages(query as any); - messages = messages.filter((m) => { - const data = m.data as Record | undefined; - if (workspaceId && data?._workspaceId && data._workspaceId !== workspaceId) { - return false; - } - if (isInternalAgent(m.from)) return false; - return Boolean(data?._isChannelMessage) || (m.to && m.to.startsWith('#')); - }); - - messages.sort((a, b) => a.ts - b.ts); - const threadSummaries = buildThreadSummaryMap(messages); - - return res.json({ - messages: messages.map((m) => { - const senderPresence = onlineUsers.get(m.from); - const fromAvatarUrl = senderPresence?.info.avatarUrl; - const fromEntityType: 'user' | 'agent' = senderPresence ? 'user' : 'agent'; - const summaryFromReplies = threadSummaries.get(m.id); - const threadSummary = summaryFromReplies ?? (m.replyCount && m.replyCount > 0 - ? { - threadId: m.id, - replyCount: m.replyCount, - participants: [], - lastReplyAt: m.ts, - } - : undefined); - return { - id: m.id, - channelId, - from: m.from, - fromEntityType, - fromAvatarUrl, - content: m.body, - timestamp: new Date(m.ts).toISOString(), - threadId: m.thread || undefined, - threadSummary, - isRead: true, - }; - }), - hasMore: messages.length === limit, - }); - } catch (err) { - console.error('[channels] Failed to fetch channel messages', err); - return res.status(500).json({ error: 'Failed to fetch channel messages' }); - } - }); - - app.post('/api/channels/subscribe', express.json(), async (req, res) => { - const { username, channels } = req.body; - - if (!username) { - return res.status(400).json({ error: 'username required' }); - } - - try { - const joinedChannels: string[] = []; - const channelList = channels || ['#general']; - - let regAttempts = 0; - const maxRegAttempts = 20; - while (!userBridge?.isUserRegistered(username) && regAttempts < maxRegAttempts) { - await new Promise((r) => setTimeout(r, 100)); - regAttempts++; - } - - if (userBridge?.isUserRegistered(username)) { - for (const channel of channelList) { - const channelId = channel.startsWith('dm:') - ? channel - : (channel.startsWith('#') ? channel : `#${channel}`); - const joined = await userBridge.joinChannel(username, channelId); - if (joined) { - joinedChannels.push(channelId); - } - } - return res.json({ success: true, channels: joinedChannels }); - } - - const client = await getRelayClient(username); - if (!client) { - return res.status(503).json({ error: 'Could not acquire relay adapter client' }); - } - - let attempts = 0; - while (client.state !== 'READY' && attempts < 50) { - await new Promise((r) => setTimeout(r, 100)); - attempts++; - } - - if (client.state !== 'READY') { - return res.status(503).json({ error: 'Relay client not ready' }); - } - - for (const channel of channelList) { - const channelId = channel.startsWith('dm:') - ? channel - : (channel.startsWith('#') ? channel : `#${channel}`); - const joined = client.joinChannel(channelId, username); - if (joined) { - joinedChannels.push(channelId); - } - } - - return res.json({ success: true, channels: joinedChannels }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Subscribe failed'; - return res.status(500).json({ error: message }); - } - }); - - app.post('/api/channels/message', express.json(), async (req, res) => { - const { username, channel, body, thread, attachmentIds } = req.body; - - if (!username || !channel || !body) { - return res.status(400).json({ error: 'username, channel, and body required' }); - } - - let attachments: Attachment[] | undefined; - if (attachmentIds && Array.isArray(attachmentIds) && attachmentIds.length > 0) { - attachments = []; - for (const id of attachmentIds) { - const attachment = attachmentRegistry.get(id); - if (attachment) { - attachments.push(attachment); - } - } - } - - const workspaceId = resolveWorkspaceId(req); - const channelId = channel.startsWith('dm:') - ? channel - : (channel.startsWith('#') ? channel : `#${channel}`); - - let success = false; - - const isLocalUser = userBridge?.isUserRegistered(username); - if (isLocalUser) { - success = await userBridge?.sendChannelMessage(username, channelId, body, { - thread, - data: workspaceId ? { _workspaceId: workspaceId } : undefined, - attachments, - }) ?? false; - } - - if (!success) { - try { - const client = await getRelayClient(username); - if (client && client.state === 'READY') { - client.joinChannel(channelId, username); - success = client.sendChannelMessage(channelId, body, { - thread, - data: workspaceId ? { _workspaceId: workspaceId } : undefined, - attachments, - }); - } - } catch { - // Ignore fallback errors. - } - } - - return res.json({ success }); - }); - - app.post('/api/channels/archive', express.json(), async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - const { channel } = req.body; - if (!channel) { - return res.status(400).json({ error: 'channel required' }); - } - const workspaceId = resolveWorkspaceId(req); - try { - await storage.saveMessage({ - id: `state-${Date.now()}`, - ts: Date.now(), - from: '__system__', - to: channel, - topic: undefined, - kind: 'message', - body: 'STATE:archived', - data: { - _channelState: 'archived', - ...(workspaceId ? { _workspaceId: workspaceId } : {}), - }, - status: 'read', - is_urgent: false, - is_broadcast: true, - }); - return res.json({ success: true }); - } catch (err) { - console.error('[channels] Failed to archive channel', err); - return res.status(500).json({ error: 'Failed to archive channel' }); - } - }); - - app.post('/api/channels/unarchive', express.json(), async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - const { channel } = req.body; - if (!channel) { - return res.status(400).json({ error: 'channel required' }); - } - const workspaceId = resolveWorkspaceId(req); - try { - await storage.saveMessage({ - id: `state-${Date.now()}`, - ts: Date.now(), - from: '__system__', - to: channel, - topic: undefined, - kind: 'message', - body: 'STATE:active', - data: { - _channelState: 'active', - ...(workspaceId ? { _workspaceId: workspaceId } : {}), - }, - status: 'read', - is_urgent: false, - is_broadcast: true, - }); - return res.json({ success: true }); - } catch (err) { - console.error('[channels] Failed to unarchive channel', err); - return res.status(500).json({ error: 'Failed to unarchive channel' }); - } - }); - - app.post('/api/dm', express.json(), async (req, res) => { - const { from, to, body, thread } = req.body; - if (!from || !to || !body) { - return res.status(400).json({ error: 'from, to, and body required' }); - } - try { - const success = await userBridge?.sendDirectMessage(from, to, body, { thread }); - return res.json({ success }); - } catch (err) { - const message = err instanceof Error ? err.message : 'DM failed'; - return res.status(500).json({ error: message }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/decisions.ts b/packages/dashboard-server/src/routes/decisions.ts deleted file mode 100644 index fae6f0c..0000000 --- a/packages/dashboard-server/src/routes/decisions.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { Application } from 'express'; - -export interface Decision { - id: string; - agentName: string; - title: string; - description: string; - options?: { id: string; label: string; description?: string }[]; - urgency: 'low' | 'medium' | 'high' | 'critical'; - category: 'approval' | 'choice' | 'input' | 'confirmation'; - createdAt: string; - expiresAt?: string; - context?: Record; -} - -interface RelayClientLike { - sendMessage: ( - to: string, - body: string, - kind?: string, - data?: unknown, - thread?: string - ) => boolean; -} - -export interface DecisionsRouteDeps { - decisions: Map; - getRelayClient: (senderName?: string, entityType?: 'agent' | 'user') => Promise; - broadcastData: () => Promise; -} - -/** - * Human decision queue routes. - */ -export function registerDecisionsRoutes(app: Application, deps: DecisionsRouteDeps): void { - const { decisions, getRelayClient, broadcastData } = deps; - - // GET /api/decisions - List all pending decisions. - app.get('/api/decisions', (_req, res) => { - const allDecisions = Array.from(decisions.values()) - .sort((a, b) => { - const urgencyOrder = { critical: 0, high: 1, medium: 2, low: 3 }; - return urgencyOrder[a.urgency] - urgencyOrder[b.urgency]; - }); - return res.json({ success: true, decisions: allDecisions }); - }); - - // POST /api/decisions - Create a new decision request. - app.post('/api/decisions', (req, res) => { - const { agentName, title, description, options, urgency, category, expiresAt, context } = req.body; - - if (!agentName || !title || !urgency || !category) { - return res.status(400).json({ - success: false, - error: 'Missing required fields: agentName, title, urgency, category', - }); - } - - const decision: Decision = { - id: `decision-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, - agentName, - title, - description: description || '', - options, - urgency, - category, - createdAt: new Date().toISOString(), - expiresAt, - context, - }; - - decisions.set(decision.id, decision); - broadcastData().catch(() => {}); - return res.json({ success: true, decision }); - }); - - // POST /api/decisions/:id/approve - Approve/resolve a decision. - app.post('/api/decisions/:id/approve', async (req, res) => { - const { id } = req.params; - const { optionId, response } = req.body; - - const decision = decisions.get(id); - if (!decision) { - return res.status(404).json({ success: false, error: 'Decision not found' }); - } - - const agentName = decision.agentName; - let responseMessage = `DECISION APPROVED: ${decision.title}`; - if (optionId && decision.options) { - const option = decision.options.find((o) => o.id === optionId); - if (option) { - responseMessage += `\nSelected: ${option.label}`; - } - } - if (response) { - responseMessage += `\nResponse: ${response}`; - } - - try { - const client = await getRelayClient('Dashboard'); - if (client) { - await client.sendMessage(agentName, responseMessage, 'message'); - } - } catch (err) { - console.warn('[api] Could not send decision response to agent:', err); - } - - decisions.delete(id); - broadcastData().catch(() => {}); - return res.json({ success: true, message: 'Decision approved' }); - }); - - // POST /api/decisions/:id/reject - Reject a decision. - app.post('/api/decisions/:id/reject', async (req, res) => { - const { id } = req.params; - const { reason } = req.body; - - const decision = decisions.get(id); - if (!decision) { - return res.status(404).json({ success: false, error: 'Decision not found' }); - } - - const agentName = decision.agentName; - let responseMessage = `DECISION REJECTED: ${decision.title}`; - if (reason) { - responseMessage += `\nReason: ${reason}`; - } - - try { - const client = await getRelayClient('Dashboard'); - if (client) { - await client.sendMessage(agentName, responseMessage, 'message'); - } - } catch (err) { - console.warn('[api] Could not send decision rejection to agent:', err); - } - - decisions.delete(id); - broadcastData().catch(() => {}); - return res.json({ success: true, message: 'Decision rejected' }); - }); - - // DELETE /api/decisions/:id - Delete/dismiss a decision. - app.delete('/api/decisions/:id', (_req, res) => { - const { id } = _req.params; - - if (!decisions.has(id)) { - return res.status(404).json({ success: false, error: 'Decision not found' }); - } - - decisions.delete(id); - broadcastData().catch(() => {}); - return res.json({ success: true, message: 'Decision dismissed' }); - }); -} diff --git a/packages/dashboard-server/src/routes/fleet.ts b/packages/dashboard-server/src/routes/fleet.ts deleted file mode 100644 index 1f4f569..0000000 --- a/packages/dashboard-server/src/routes/fleet.ts +++ /dev/null @@ -1,173 +0,0 @@ -import crypto from 'crypto'; -import fs from 'fs'; -import path from 'path'; -import type { Application } from 'express'; -import type { WebSocketServer } from 'ws'; -import type { Decision } from './decisions.js'; -import type { TaskAssignment } from './tasks.js'; - -interface ActiveWorkerLike { - name: string; - status?: string; -} - -interface SpawnReaderLike { - getActiveWorkers(): ActiveWorkerLike[]; -} - -interface FleetServer { - id: string; - name: string; - status: 'healthy' | 'degraded' | 'offline'; - agents: { name: string; status: string }[]; - cpuUsage: number; - memoryUsage: number; - activeConnections: number; - uptime: number; - lastHeartbeat: string; -} - -export interface FleetRouteDeps { - dataDir: string; - spawnReader?: SpawnReaderLike; - wss: WebSocketServer; - decisions: Map; - tasks: Map; -} - -async function loadAgentStatuses(dataDir: string): Promise> { - const agentsFile = path.join(dataDir, 'agents.json'); - try { - if (fs.existsSync(agentsFile)) { - const data = JSON.parse(fs.readFileSync(agentsFile, 'utf-8')); - const result: Record = {}; - for (const agent of data.agents || []) { - if (agent && typeof agent.name === 'string') { - result[agent.name] = { status: agent.status || 'offline' }; - } - } - return result; - } - } catch (err) { - console.warn('[api] Failed to load agent statuses:', err); - } - return {}; -} - -/** - * Fleet overview and aggregate stats routes. - */ -export function registerFleetRoutes(app: Application, deps: FleetRouteDeps): void { - const { dataDir, spawnReader, wss, decisions, tasks } = deps; - - // GET /api/fleet/servers - Get fleet server overview. - app.get('/api/fleet/servers', async (_req, res) => { - const servers: FleetServer[] = []; - const localAgents = spawnReader?.getActiveWorkers() || []; - const agentStatuses = await loadAgentStatuses(dataDir); - let hasBridgeProjects = false; - - const bridgeStatePath = path.join(dataDir, 'bridge-state.json'); - if (fs.existsSync(bridgeStatePath)) { - try { - const bridgeState = JSON.parse(fs.readFileSync(bridgeStatePath, 'utf-8')); - if (bridgeState.projects && bridgeState.projects.length > 0) { - hasBridgeProjects = true; - - for (const project of bridgeState.projects) { - let projectAgents: { name: string; status: string }[] = []; - - if (project.path) { - const projectHash = crypto.createHash('sha256').update(project.path).digest('hex').slice(0, 12); - const projectDataDir = path.join(path.dirname(dataDir), projectHash); - const projectTeamDir = path.join(projectDataDir, 'team'); - const agentsPath = path.join(projectTeamDir, 'agents.json'); - - if (fs.existsSync(agentsPath)) { - try { - const agentsData = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')); - if (agentsData.agents && Array.isArray(agentsData.agents)) { - const thirtySecondsAgo = Date.now() - 30 * 1000; - projectAgents = agentsData.agents - .filter((a: { lastSeen?: string }) => { - if (!a.lastSeen) return false; - return new Date(a.lastSeen).getTime() > thirtySecondsAgo; - }) - .map((a: { name: string }) => ({ - name: a.name, - status: 'online', - })); - } - } catch (e) { - console.warn(`[api] Failed to read agents for ${project.path}:`, e); - } - } - } - - servers.push({ - id: project.id, - name: project.name || project.path.split('/').pop() || project.id, - status: project.connected ? 'healthy' : 'offline', - agents: projectAgents, - cpuUsage: 0, - memoryUsage: 0, - activeConnections: project.connected ? 1 : 0, - uptime: 0, - lastHeartbeat: project.lastSeen || new Date().toISOString(), - }); - } - } - } catch (err) { - console.warn('[api] Failed to read bridge state:', err); - } - } - - if (!hasBridgeProjects) { - servers.push({ - id: 'local', - name: 'Local Daemon', - status: 'healthy', - agents: localAgents.map((a) => ({ - name: a.name, - status: agentStatuses[a.name]?.status || 'unknown', - })), - cpuUsage: Math.random() * 30, - memoryUsage: Math.random() * 50, - activeConnections: wss.clients.size, - uptime: process.uptime(), - lastHeartbeat: new Date().toISOString(), - }); - } - - return res.json({ success: true, servers }); - }); - - // GET /api/fleet/stats - Get aggregate fleet statistics. - app.get('/api/fleet/stats', async (_req, res) => { - const localAgents = spawnReader?.getActiveWorkers() || []; - const agentStatuses = await loadAgentStatuses(dataDir); - - const totalAgents = localAgents.length; - let onlineAgents = 0; - let busyAgents = 0; - - for (const agent of localAgents) { - const status = agentStatuses[agent.name]?.status; - if (status === 'online') onlineAgents++; - if (status === 'busy') busyAgents++; - } - - return res.json({ - success: true, - stats: { - totalAgents, - onlineAgents, - busyAgents, - pendingDecisions: decisions.size, - activeTasks: Array.from(tasks.values()).filter((t) => - t.status === 'assigned' || t.status === 'in_progress' - ).length, - }, - }); - }); -} diff --git a/packages/dashboard-server/src/routes/history.ts b/packages/dashboard-server/src/routes/history.ts deleted file mode 100644 index 0e73cef..0000000 --- a/packages/dashboard-server/src/routes/history.ts +++ /dev/null @@ -1,262 +0,0 @@ -import type { Application } from 'express'; -import type { StorageAdapter } from '@agent-relay/storage/adapter'; - -export interface HistoryRouteDeps { - storage?: StorageAdapter; - formatDuration: (startMs: number, endMs?: number) => string; - isInternalAgent: (name: string) => boolean; - remapAgentName: (name: string) => string; -} - -/** - * Conversation history and storage statistics routes. - */ -export function registerHistoryRoutes(app: Application, deps: HistoryRouteDeps): void { - const { storage, formatDuration, isInternalAgent, remapAgentName } = deps; - - // GET /api/history/sessions - List all sessions with filters. - app.get('/api/history/sessions', async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - try { - const query: { - agentName?: string; - since?: number; - limit?: number; - } = {}; - - if (req.query.agent && typeof req.query.agent === 'string') { - query.agentName = req.query.agent; - } - if (req.query.since) { - query.since = parseInt(req.query.since as string, 10); - } - query.limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 50; - - const sessions = storage.getSessions - ? await storage.getSessions(query) - : []; - - const result = sessions.map((s) => ({ - id: s.id, - agentName: s.agentName, - cli: s.cli, - startedAt: new Date(s.startedAt).toISOString(), - endedAt: s.endedAt ? new Date(s.endedAt).toISOString() : undefined, - duration: formatDuration(s.startedAt, s.endedAt), - messageCount: s.messageCount, - summary: s.summary, - isActive: !s.endedAt, - closedBy: s.closedBy, - })); - - return res.json({ sessions: result }); - } catch (err) { - console.error('Failed to fetch sessions', err); - return res.status(500).json({ error: 'Failed to fetch sessions' }); - } - }); - - // GET /api/history/messages - Get messages with filters. - app.get('/api/history/messages', async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - try { - const query: { - from?: string; - to?: string; - thread?: string; - sinceTs?: number; - limit?: number; - order?: 'asc' | 'desc'; - } = {}; - - if (req.query.from && typeof req.query.from === 'string') { - query.from = req.query.from; - } - if (req.query.to && typeof req.query.to === 'string') { - query.to = req.query.to; - } - if (req.query.thread && typeof req.query.thread === 'string') { - query.thread = req.query.thread; - } - if (req.query.since) { - query.sinceTs = parseInt(req.query.since as string, 10); - } - query.limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 100; - query.order = (req.query.order as 'asc' | 'desc') || 'desc'; - - let messages = await storage.getMessages(query); - - // Filter out messages from/to internal system agents (e.g., __spawner__). - messages = messages.filter((m) => !isInternalAgent(m.from) && !isInternalAgent(m.to)); - - // Client-side search filter (basic substring match). - const searchTerm = req.query.search as string | undefined; - if (searchTerm && searchTerm.trim()) { - const lowerSearch = searchTerm.toLowerCase(); - messages = messages.filter((m) => - m.body.toLowerCase().includes(lowerSearch) || - m.from.toLowerCase().includes(lowerSearch) || - m.to.toLowerCase().includes(lowerSearch), - ); - } - - const result = messages.map((m) => ({ - id: m.id, - from: remapAgentName(m.from), - to: remapAgentName(m.to), - content: m.body, - timestamp: new Date(m.ts).toISOString(), - thread: m.thread, - isBroadcast: m.is_broadcast, - isUrgent: m.is_urgent, - status: m.status, - })); - - return res.json({ messages: result }); - } catch (err) { - console.error('Failed to fetch messages', err); - return res.status(500).json({ error: 'Failed to fetch messages' }); - } - }); - - // GET /api/history/conversations - Get unique conversations (agent pairs). - app.get('/api/history/conversations', async (_req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - try { - // Get all messages to build conversation list. - const messages = await storage.getMessages({ limit: 1000, order: 'desc' }); - - // Build unique conversation pairs. - const conversationMap = new Map(); - - for (const msg of messages) { - // Skip broadcasts for conversation pairing. - if (msg.to === '*' || msg.is_broadcast) continue; - - // Skip messages from/to internal system agents (e.g., __spawner__). - if (isInternalAgent(msg.from) || isInternalAgent(msg.to)) continue; - - // Create normalized key (sorted participants, with display names). - const participants = [remapAgentName(msg.from), remapAgentName(msg.to)].sort(); - const key = participants.join(':'); - - const existing = conversationMap.get(key); - if (existing) { - existing.messageCount++; - } else { - conversationMap.set(key, { - participants, - lastMessage: msg.body.substring(0, 100), - lastTimestamp: new Date(msg.ts).toISOString(), - messageCount: 1, - }); - } - } - - // Convert to array sorted by last timestamp. - const conversations = Array.from(conversationMap.values()) - .sort((a, b) => new Date(b.lastTimestamp).getTime() - new Date(a.lastTimestamp).getTime()); - - return res.json({ conversations }); - } catch (err) { - console.error('Failed to fetch conversations', err); - return res.status(500).json({ error: 'Failed to fetch conversations' }); - } - }); - - // GET /api/history/message/:id - Get a single message by ID. - app.get('/api/history/message/:id', async (req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - try { - const { id } = req.params; - const message = storage.getMessageById - ? await storage.getMessageById(id) - : null; - - if (!message) { - return res.status(404).json({ error: 'Message not found' }); - } - - return res.json({ - id: message.id, - from: message.from, - to: message.to, - content: message.body, - timestamp: new Date(message.ts).toISOString(), - thread: message.thread, - isBroadcast: message.is_broadcast, - isUrgent: message.is_urgent, - status: message.status, - data: message.data, - }); - } catch (err) { - console.error('Failed to fetch message', err); - return res.status(500).json({ error: 'Failed to fetch message' }); - } - }); - - // GET /api/history/stats - Get storage statistics. - app.get('/api/history/stats', async (_req, res) => { - if (!storage) { - return res.status(503).json({ error: 'Storage not configured' }); - } - - try { - // Get stats from adapter if available (SQLite-specific getStats method). - const storageWithStats = storage as { - getStats?: () => Promise<{ - messageCount: number; - sessionCount: number; - oldestMessageTs?: number; - }>; - }; - if (typeof storageWithStats.getStats === 'function' && typeof storage.getSessions === 'function') { - const stats = await storageWithStats.getStats(); - const sessions = await storage.getSessions({ limit: 1000 }); - - // Calculate additional stats. - const activeSessions = sessions.filter((s) => !s.endedAt).length; - const uniqueAgents = new Set(sessions.map((s) => s.agentName)).size; - - return res.json({ - messageCount: stats.messageCount, - sessionCount: stats.sessionCount, - activeSessions, - uniqueAgents, - oldestMessageDate: stats.oldestMessageTs - ? new Date(stats.oldestMessageTs).toISOString() - : null, - }); - } - - // Basic stats for other adapters. - const messages = await storage.getMessages({ limit: 1 }); - return res.json({ - messageCount: messages.length > 0 ? 'unknown' : 0, - sessionCount: 'unknown', - activeSessions: 'unknown', - uniqueAgents: 'unknown', - }); - } catch (err) { - console.error('Failed to fetch stats', err); - return res.status(500).json({ error: 'Failed to fetch stats' }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/messaging.ts b/packages/dashboard-server/src/routes/messaging.ts deleted file mode 100644 index cf629f9..0000000 --- a/packages/dashboard-server/src/routes/messaging.ts +++ /dev/null @@ -1,422 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import crypto from 'crypto'; -import type { Application } from 'express'; -import type { StorageAdapter, StoredMessage } from '@agent-relay/storage/adapter'; - -interface Attachment { - id: string; - filename: string; - mimeType: string; - size: number; - url: string; - filePath?: string; - width?: number; - height?: number; - data?: string; -} - -interface RelayClientLike { - state: string; - sendMessage: ( - to: string, - body: string, - kind?: string, - data?: unknown, - thread?: string - ) => boolean; -} - -interface SendRequestBody { - to?: string; - message?: string; - thread?: string; - attachments?: string[]; - from?: string; -} - -interface BridgeSendRequestBody { - projectId?: string; - to?: string; - message?: string; -} - -interface UploadRequestBody { - filename?: string; - mimeType?: string; - data?: string; -} - -export interface MessagingRouteDeps { - getTeamMembers: (teamName: string) => string[]; - isAgentOnline: (agentName: string) => boolean; - isRecipientOnline: (recipient: string) => boolean; - getRelayClient: ( - senderName?: string, - entityType?: 'agent' | 'user' - ) => Promise; - attachmentRegistry: Map; - attachmentsDir: string; - broadcastData: () => Promise; - storage?: StorageAdapter; - remapAgentName?: (name: string) => string; -} - -/** - * Integrated messaging and attachment routes. - */ -export function registerMessagingRoutes(app: Application, deps: MessagingRouteDeps): void { - const { - getTeamMembers, - isAgentOnline, - isRecipientOnline, - getRelayClient, - attachmentRegistry, - attachmentsDir, - broadcastData, - storage, - remapAgentName, - } = deps; - - const mapStoredMessage = (message: StoredMessage) => ({ - id: message.id, - from: remapAgentName ? remapAgentName(message.from) : message.from, - to: remapAgentName ? remapAgentName(message.to) : message.to, - content: message.body, - timestamp: new Date(message.ts).toISOString(), - thread: message.thread, - replyCount: message.replyCount, - }); - - const parseBeforeCursor = (raw: unknown): number | undefined => { - if (typeof raw !== 'string' || raw.trim() === '') { - return undefined; - } - const asNumber = Number.parseInt(raw, 10); - if (Number.isFinite(asNumber)) { - return asNumber; - } - const asTime = Date.parse(raw); - return Number.isNaN(asTime) ? undefined : asTime; - }; - - const findParentMessage = async (id: string): Promise => { - if (!storage) { - return null; - } - - if (typeof storage.getMessageById === 'function') { - const exact = await storage.getMessageById(id); - if (exact) { - return exact; - } - } - - const candidates = await storage.getMessages({ limit: 2000, order: 'desc' }); - return candidates.find((message) => message.id === id || message.id.startsWith(id)) ?? null; - }; - - const resolveReplyTarget = (parent: StoredMessage, senderName: string): string => { - const from = parent.from; - const to = parent.to; - const channelFromData = - typeof (parent.data as { channel?: unknown } | undefined)?.channel === 'string' - ? ((parent.data as { channel: string }).channel) - : undefined; - - if (to.startsWith('#')) { - return to; - } - if (to === '*' && channelFromData && channelFromData.startsWith('#')) { - return channelFromData; - } - return from === senderName ? to : from; - }; - - // Thread replies (non-mock mode) - app.get('/api/messages/:id/replies', async (req, res) => { - if (!storage) { - return res.status(503).json({ ok: false, error: 'Storage not configured' }); - } - - const { id } = req.params; - const limitRaw = typeof req.query.limit === 'string' ? Number.parseInt(req.query.limit, 10) : 50; - const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : 50; - const beforeTs = parseBeforeCursor(req.query.before); - - try { - const parent = await findParentMessage(id); - if (!parent) { - return res.status(404).json({ ok: false, error: 'Message not found' }); - } - - const allReplies = await storage.getMessages({ thread: parent.id, order: 'asc' }); - const filteredReplies = beforeTs ? allReplies.filter((message) => message.ts < beforeTs) : allReplies; - const replies = filteredReplies.length > limit ? filteredReplies.slice(-limit) : filteredReplies; - - const hasMore = filteredReplies.length > replies.length; - const nextCursor = hasMore && replies.length > 0 ? new Date(replies[0].ts).toISOString() : undefined; - - return res.json({ - ok: true, - data: { - parent: { - ...mapStoredMessage(parent), - reply_count: allReplies.length, - }, - replies: replies.map(mapStoredMessage), - nextCursor, - }, - }); - } catch (err) { - console.error(`[dashboard] Failed to load thread replies for ${id}:`, err); - return res.status(500).json({ ok: false, error: 'Failed to load thread replies' }); - } - }); - - app.post('/api/messages/:id/replies', async (req, res) => { - if (!storage) { - return res.status(503).json({ ok: false, error: 'Storage not configured' }); - } - - const { id } = req.params; - const { text, from } = req.body || {}; - const trimmedText = typeof text === 'string' ? text.trim() : ''; - - if (!trimmedText) { - return res.status(400).json({ ok: false, error: 'Missing "text" field' }); - } - - try { - const parent = await findParentMessage(id); - if (!parent) { - return res.status(404).json({ ok: false, error: 'Message not found' }); - } - - const senderName = typeof from === 'string' && from.trim() ? from.trim() : 'Dashboard'; - const relayClient = await getRelayClient(senderName, 'user'); - if (!relayClient || relayClient.state !== 'READY') { - return res.status(503).json({ ok: false, error: 'Relay adapter is not ready' }); - } - - const target = resolveReplyTarget(parent, senderName); - const sent = relayClient.sendMessage(target, trimmedText, 'message', undefined, parent.id); - if (!sent) { - return res.status(500).json({ ok: false, error: 'Failed to send reply' }); - } - - const reply = { - id: `pending-reply-${Date.now()}`, - from: senderName, - to: target, - content: trimmedText, - timestamp: new Date().toISOString(), - thread: parent.id, - }; - - broadcastData().catch((err) => console.error('[dashboard] Failed to broadcast after reply:', err)); - return res.status(201).json({ ok: true, data: reply }); - } catch (err) { - console.error(`[dashboard] Failed to post thread reply for ${id}:`, err); - return res.status(500).json({ ok: false, error: 'Failed to post reply' }); - } - }); - - // API endpoint to send messages. - app.post('/api/send', async (req, res) => { - const { to, message, thread, attachments: attachmentIds, from: senderName } = req.body as SendRequestBody; - - if (!to || !message) { - return res.status(400).json({ error: 'Missing "to" or "message" field' }); - } - - // Check if this is a team mention (team:teamName). - const teamMatch = to.match(/^team:(.+)$/); - let targets: string[]; - - if (teamMatch) { - const teamName = teamMatch[1]; - const members = getTeamMembers(teamName); - if (members.length === 0) { - return res.status(404).json({ error: `No agents found in team "${teamName}"` }); - } - // Filter to only online members. - targets = members.filter(isAgentOnline); - if (targets.length === 0) { - return res.status(404).json({ error: `No online agents in team "${teamName}"` }); - } - } else { - // Fail fast if target agent is offline (except broadcasts). - if (to !== '*' && !isRecipientOnline(to)) { - return res.status(404).json({ error: `Recipient "${to}" is not online` }); - } - targets = [to]; - } - - // Always use 'Dashboard' client to avoid name conflicts with user agents. - const relayClient = await getRelayClient('Dashboard'); - if (!relayClient || relayClient.state !== 'READY') { - return res.status(503).json({ error: 'Relay adapter is not ready' }); - } - - try { - // Resolve attachments if provided. - let attachments: Attachment[] | undefined; - if (attachmentIds && Array.isArray(attachmentIds) && attachmentIds.length > 0) { - attachments = []; - for (const id of attachmentIds) { - const attachment = attachmentRegistry.get(id); - if (attachment) { - attachments.push(attachment); - } - } - } - - // Include attachments, channel context, and sender info in the message data field. - const isBroadcast = targets.length === 1 && targets[0] === '*'; - const messageData: Record = {}; - - if (attachments && attachments.length > 0) { - messageData.attachments = attachments; - } - - if (isBroadcast) { - messageData.channel = 'general'; - } - - // Include actual sender name for dashboard messages for UI attribution. - if (senderName) { - messageData.senderName = senderName; - } - - const hasMessageData = Object.keys(messageData).length > 0; - - // Send to all targets (single agent, team members, or broadcast). - let allSent = true; - for (const target of targets) { - const sent = relayClient.sendMessage(target, message, 'message', hasMessageData ? messageData : undefined, thread); - if (!sent) { - allSent = false; - console.error(`[dashboard] Failed to send message to ${target}`); - } - } - - if (allSent) { - // Broadcast updated data to all connected clients so they see the sent message. - broadcastData().catch((err) => console.error('[dashboard] Failed to broadcast after send:', err)); - return res.json({ success: true, sentTo: targets.length > 1 ? targets : targets[0] }); - } - - return res.status(500).json({ error: 'Failed to send message to some recipients' }); - } catch (err) { - console.error('[dashboard] Failed to send message:', err); - return res.status(500).json({ error: 'Failed to send message' }); - } - }); - - // API endpoint to send messages via bridge (cross-project). - app.post('/api/bridge/send', async (req, res) => { - const { projectId, to, message } = req.body as BridgeSendRequestBody; - - if (!projectId || !to || !message) { - return res.status(400).json({ error: 'Missing "projectId", "to", or "message" field' }); - } - - return res.status(501).json({ - error: 'Legacy socket bridge sending has been removed. Use broker/relaycast messaging paths instead.', - }); - }); - - // API endpoint to upload attachments (images/screenshots). - app.post('/api/upload', async (req, res) => { - const { filename, mimeType, data } = req.body as UploadRequestBody; - - // Validate required fields. - if (!filename || !mimeType || !data) { - return res.status(400).json({ - success: false, - error: 'Missing required fields: filename, mimeType, data', - }); - } - - // Validate mime type (only allow images for now). - const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml']; - if (!allowedTypes.includes(mimeType)) { - return res.status(400).json({ - success: false, - error: `Invalid file type. Allowed types: ${allowedTypes.join(', ')}`, - }); - } - - try { - // Decode base64 data. - const base64Data = data.replace(/^data:[^;]+;base64,/, ''); - const buffer = Buffer.from(base64Data, 'base64'); - - // Generate unique ID and filename for the attachment. - const attachmentId = crypto.randomUUID(); - const timestamp = Date.now(); - const ext = mimeType.split('/')[1].replace('svg+xml', 'svg'); - const safeFilename = `${attachmentId.substring(0, 8)}-${timestamp}.${ext}`; - - // Save to ~/.relay/attachments/ directory for agents to access. - const attachmentFilePath = path.join(attachmentsDir, safeFilename); - fs.writeFileSync(attachmentFilePath, buffer); - - // Create attachment record with file path for agents. - const attachment: Attachment = { - id: attachmentId, - filename, - mimeType, - size: buffer.length, - url: `/attachments/${safeFilename}`, - filePath: attachmentFilePath, - data, - }; - - // Store in registry for lookup when sending messages. - attachmentRegistry.set(attachmentId, attachment); - - console.log(`[dashboard] Uploaded attachment: ${filename} (${buffer.length} bytes) -> ${attachmentFilePath}`); - - return res.json({ - success: true, - attachment: { - id: attachment.id, - filename: attachment.filename, - mimeType: attachment.mimeType, - size: attachment.size, - url: attachment.url, - filePath: attachment.filePath, - }, - }); - } catch (err) { - console.error('[dashboard] Upload failed:', err); - return res.status(500).json({ - success: false, - error: 'Failed to upload file', - }); - } - }); - - // API endpoint to get attachment by ID. - app.get('/api/attachment/:id', (req, res) => { - const { id } = req.params; - const attachment = attachmentRegistry.get(id); - - if (!attachment) { - return res.status(404).json({ error: 'Attachment not found' }); - } - return res.json({ - success: true, - attachment: { - id: attachment.id, - filename: attachment.filename, - mimeType: attachment.mimeType, - size: attachment.size, - url: attachment.url, - filePath: attachment.filePath, - }, - }); - }); -} diff --git a/packages/dashboard-server/src/routes/settings.ts b/packages/dashboard-server/src/routes/settings.ts deleted file mode 100644 index 7ce941b..0000000 --- a/packages/dashboard-server/src/routes/settings.ts +++ /dev/null @@ -1,127 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import type { Application } from 'express'; - -/** - * Workspace settings and trajectory settings routes. - */ -export function registerSettingsRoutes(app: Application): void { - // GET /api/settings - Get all workspace settings with documentation. - app.get('/api/settings', async (_req, res) => { - try { - const { readRelayConfig, shouldStoreInRepo, getTrajectoriesStorageDescription } = await import('@agent-relay/config/trajectory-config'); - const config = readRelayConfig(); - - return res.json({ - success: true, - settings: { - trajectories: { - storeInRepo: shouldStoreInRepo(), - storageLocation: getTrajectoriesStorageDescription(), - description: 'Trajectories record the journey of agent work using the PDERO paradigm (Plan, Design, Execute, Review, Observe). They capture decisions, phase transitions, and retrospectives.', - benefits: [ - 'Track why decisions were made, not just what was built', - 'Enable session recovery when agents crash or context is lost', - 'Provide learning data for future agents working on similar tasks', - 'Create an audit trail of agent work for review', - ], - learnMore: 'https://pdero.com', - optInReason: 'Enable "Store in repo" to version-control your trajectories alongside your code. This is useful for teams who want to review agent decision-making processes.', - }, - }, - config, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Settings error:', err); - return res.status(500).json({ - success: false, - error: message, - }); - } - }); - - // GET /api/settings/trajectory - Get trajectory storage settings. - app.get('/api/settings/trajectory', async (_req, res) => { - try { - const { readRelayConfig, shouldStoreInRepo, getTrajectoriesStorageDescription } = await import('@agent-relay/config/trajectory-config'); - const config = readRelayConfig(); - - return res.json({ - success: true, - settings: { - storeInRepo: shouldStoreInRepo(), - storageLocation: getTrajectoriesStorageDescription(), - }, - config: config.trajectories || {}, - documentation: { - title: 'Trajectory Storage', - description: 'Trajectories record the journey of agent work using the PDERO paradigm (Plan, Design, Execute, Review, Observe).', - whatIsIt: 'A trajectory captures not just what an agent built, but WHY it made specific decisions. This includes phase transitions, key decisions with reasoning, and retrospective summaries.', - benefits: [ - 'Understand agent decision-making for code review', - 'Enable session recovery if agents crash', - 'Train future agents on your codebase patterns', - 'Create audit trails of AI work', - ], - storeInRepoExplanation: 'When enabled, trajectories are stored in .trajectories/ in your repo and can be committed to source control. When disabled (default), they are stored in your user directory (~/.config/agent-relay/trajectories/).', - learnMore: 'https://pdero.com', - }, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Settings trajectory error:', err); - return res.status(500).json({ - success: false, - error: message, - }); - } - }); - - // PUT /api/settings/trajectory - Update trajectory storage settings. - app.put('/api/settings/trajectory', async (req, res) => { - try { - const { storeInRepo } = req.body; - - if (typeof storeInRepo !== 'boolean') { - return res.status(400).json({ - success: false, - error: 'storeInRepo must be a boolean', - }); - } - - const { getRelayConfigPath, readRelayConfig } = await import('@agent-relay/config/trajectory-config'); - const { getProjectPaths } = await import('@agent-relay/config'); - const { projectRoot: _projectRoot } = getProjectPaths(); - - const config = readRelayConfig(); - config.trajectories = { - ...config.trajectories, - storeInRepo, - }; - - const configPath = getRelayConfigPath(); - const configDir = path.dirname(configPath); - if (!fs.existsSync(configDir)) { - fs.mkdirSync(configDir, { recursive: true }); - } - - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - - return res.json({ - success: true, - settings: { - storeInRepo, - storageLocation: storeInRepo ? 'repo (.trajectories/)' : 'user (~/.config/agent-relay/trajectories/)', - }, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Settings trajectory update error:', err); - return res.status(500).json({ - success: false, - error: message, - }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/spawn.ts b/packages/dashboard-server/src/routes/spawn.ts deleted file mode 100644 index 3112ab4..0000000 --- a/packages/dashboard-server/src/routes/spawn.ts +++ /dev/null @@ -1,663 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { execFile } from 'child_process'; -import type { Application } from 'express'; -import { getAgentOutboxTemplate } from '@agent-relay/config'; -import { listTrajectorySteps, getTrajectoryStatus, getTrajectoryHistory } from '@agent-relay/trajectory'; -import { fetchBrokerSpawnedAgents } from '../lib/spawned-agents.js'; - -interface ActiveWorkerLike { - name: string; - cli: string; - task: string; - team?: string; - spawnerName?: string; - spawnedAt: number; - pid?: number; -} - -interface SpawnReaderLike { - getActiveWorkers(): ActiveWorkerLike[]; - hasWorker(name: string): boolean; - getWorkerOutput(name: string, limit?: number): string[] | undefined; - getWorkerRawOutput(name: string): string | undefined; - sendWorkerInput(name: string, data: string): Promise; -} - -interface RelayAdapterLike { - spawn: (request: { - name: string; - cli: string; - task?: string; - team?: string; - model?: string; - cwd?: string; - interactive?: boolean; - shadowMode?: string; - shadowOf?: string; - spawnerName?: string; - userId?: string; - continueFrom?: string; - includeWorkflowConventions?: boolean; - }) => Promise<{ success: boolean; name?: string; error?: string }>; - release: (name: string) => Promise<{ success: boolean; name?: string; error?: string }>; -} - -interface SpawnPresenceEvent { - type: string; - agent: { name: string }; - cli?: string; - task?: string; - spawnedBy?: string; - releasedBy?: string; - timestamp: string; -} - -export interface SpawnRouteDeps { - dataDir: string; - projectRoot?: string; - relayAdapter: RelayAdapterLike; - spawnReader?: SpawnReaderLike; - agentCwdMap: Map; - isAgentOnline: (agentName: string) => boolean; - resolveWorkspaceId: (req: { - query?: Record; - body?: Record; - headers?: Record; - }) => string | undefined; - broadcastData: () => Promise; - broadcastPresence: (event: SpawnPresenceEvent) => void; -} - -/** - * Spawn, logs, repositories, and trajectory routes. - */ -export function registerSpawnRoutes(app: Application, deps: SpawnRouteDeps): void { - const { - dataDir, - projectRoot, - relayAdapter, - spawnReader, - agentCwdMap, - isAgentOnline, - resolveWorkspaceId, - broadcastData, - broadcastPresence, - } = deps; - - // GET /api/logs/:name - Get historical logs for a spawned agent. - app.get('/api/logs/:name', (req, res) => { - if (!spawnReader) { - return res.status(503).json({ error: 'Spawner not enabled' }); - } - - const { name } = req.params; - const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 500; - const raw = req.query.raw === 'true'; - - if (!spawnReader.hasWorker(name)) { - return res.status(404).json({ error: `Agent ${name} not found` }); - } - - try { - if (raw) { - const output = spawnReader.getWorkerRawOutput(name); - return res.json({ - name, - raw: true, - output: output || '', - timestamp: new Date().toISOString(), - }); - } - - const lines = spawnReader.getWorkerOutput(name, limit); - return res.json({ - name, - raw: false, - lines: lines || [], - lineCount: lines?.length || 0, - timestamp: new Date().toISOString(), - }); - } catch (err) { - console.error(`Failed to get logs for ${name}:`, err); - return res.status(500).json({ error: 'Failed to get logs' }); - } - }); - - // GET /api/logs - List all agents with available logs. - app.get('/api/logs', (_req, res) => { - if (!spawnReader) { - return res.status(503).json({ error: 'Spawner not enabled' }); - } - - try { - const workers = spawnReader.getActiveWorkers(); - const agents = workers.map((w) => ({ - name: w.name, - cli: w.cli, - pid: w.pid, - spawnedAt: new Date(w.spawnedAt).toISOString(), - hasLogs: true, - })); - return res.json({ agents }); - } catch (err) { - console.error('Failed to list agents with logs:', err); - return res.status(500).json({ error: 'Failed to list agents' }); - } - }); - - // GET /api/agents/:name/online - Check if an agent is online. - app.get('/api/agents/:name/online', (req, res) => { - const { name } = req.params; - const online = isAgentOnline(name); - return res.json({ name, online }); - }); - - // PUT /api/agents/:name/cwd - Register an agent's working directory. - app.put('/api/agents/:name/cwd', (req, res) => { - const { name } = req.params; - const { cwd } = req.body || {}; - if (!cwd || typeof cwd !== 'string') { - return res.status(400).json({ error: 'Missing required field: cwd' }); - } - agentCwdMap.set(name, cwd); - broadcastData().catch(() => {}); - return res.json({ success: true, name, cwd }); - }); - - // POST /api/spawn - Spawn a new agent. - app.post('/api/spawn', async (req, res) => { - const { - name, - cli = 'claude', - task = '', - team, - model, - spawnerName, - cwd, - interactive, - shadowMode, - shadowAgent, - shadowOf, - shadowTriggers, - shadowSpeakOn, - userId, - continueFrom, - } = req.body; - - void shadowAgent; - void shadowTriggers; - void shadowSpeakOn; - - if (!name || typeof name !== 'string') { - return res.status(400).json({ - success: false, - error: 'Missing required field: name', - }); - } - - const effectiveCwd = cwd || (spawnerName ? agentCwdMap.get(spawnerName) : undefined); - - try { - const result = await relayAdapter.spawn({ - name, - cli, - task, - team: team || undefined, - model: model || undefined, - cwd: effectiveCwd || undefined, - interactive, - shadowMode, - shadowOf, - spawnerName: spawnerName || undefined, - userId: typeof userId === 'string' ? userId : undefined, - continueFrom: typeof continueFrom === 'string' ? continueFrom : undefined, - includeWorkflowConventions: true, - }); - - if (result.success) { - if (effectiveCwd) { - agentCwdMap.set(name, effectiveCwd); - } - broadcastData().catch(() => {}); - broadcastPresence({ - type: 'agent_spawned', - agent: { name }, - cli, - task, - spawnedBy: spawnerName || 'Dashboard', - timestamp: new Date().toISOString(), - }); - } - - return res.json(result); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Spawn error:', err); - return res.status(500).json({ - success: false, - name, - error: message, - }); - } - }); - - // POST /api/repos/clone - Clone a repo into the workspace directory. - app.post('/api/repos/clone', async (req, res) => { - const { fullName } = req.body; - - if (!fullName || typeof fullName !== 'string' || !fullName.includes('/')) { - return res.status(400).json({ success: false, error: 'fullName is required (e.g., "Owner/RepoName")' }); - } - - if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(fullName)) { - return res.status(400).json({ success: false, error: 'Invalid repository name format' }); - } - - const repoName = fullName.split('/').pop(); - if (!repoName) { - return res.status(400).json({ success: false, error: 'Invalid repository name' }); - } - - const workspaceDir = process.env.WORKSPACE_DIR || path.dirname(projectRoot || dataDir); - const targetDir = path.join(workspaceDir, repoName); - - const resolvedTarget = path.resolve(targetDir); - const resolvedWorkspace = path.resolve(workspaceDir); - if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep)) { - return res.status(400).json({ success: false, error: 'Invalid path' }); - } - - if (fs.existsSync(path.join(targetDir, '.git'))) { - return res.json({ success: true, message: 'Already cloned', path: targetDir }); - } - - if (fs.existsSync(targetDir)) { - console.log(`[api/repos/clone] Removing stale directory ${targetDir} (no .git found)`); - fs.rmSync(targetDir, { recursive: true, force: true }); - } - - const cloneUrl = `https://github.com/${fullName}.git`; - - try { - await new Promise((resolve, reject) => { - execFile('git', ['clone', cloneUrl, targetDir], { timeout: 120000 }, (error, _stdout, stderr) => { - if (error) { - reject(new Error(stderr || error.message)); - } else { - resolve(); - } - }); - }); - - execFile('git', ['config', '--global', '--add', 'safe.directory', targetDir], () => {}); - return res.json({ success: true, path: targetDir }); - } catch (err) { - const safeMessage = err instanceof Error ? err.message : 'Clone failed'; - console.error('[api/repos/clone] Clone failed:', safeMessage); - try { - fs.rmSync(targetDir, { recursive: true, force: true }); - } catch { - // Ignore cleanup errors. - } - return res.status(500).json({ success: false, error: safeMessage }); - } - }); - - // POST /api/repos/remove - Remove a cloned repo directory from the workspace. - app.post('/api/repos/remove', async (req, res) => { - const { fullName } = req.body; - - if (!fullName || typeof fullName !== 'string' || !fullName.includes('/')) { - return res.status(400).json({ success: false, error: 'fullName is required (e.g., "Owner/RepoName")' }); - } - - if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(fullName)) { - return res.status(400).json({ success: false, error: 'Invalid repository name format' }); - } - - const repoName = fullName.split('/').pop(); - if (!repoName) { - return res.status(400).json({ success: false, error: 'Invalid repository name' }); - } - - const workspaceDir = process.env.WORKSPACE_DIR || path.dirname(projectRoot || dataDir); - const targetDir = path.join(workspaceDir, repoName); - - const resolvedTarget = path.resolve(targetDir); - const resolvedWorkspace = path.resolve(workspaceDir); - if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep)) { - return res.status(400).json({ success: false, error: 'Invalid path' }); - } - - if (!fs.existsSync(targetDir)) { - return res.json({ success: true, message: 'Directory does not exist', path: targetDir }); - } - - try { - fs.rmSync(targetDir, { recursive: true, force: true }); - console.log(`[api/repos/remove] Removed directory: ${targetDir}`); - return res.json({ success: true, path: targetDir }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Remove failed'; - console.error('[api/repos/remove] Remove failed:', message); - return res.status(500).json({ success: false, error: message }); - } - }); - - // POST /api/spawn/architect - Spawn an Architect agent for bridge mode. - app.post('/api/spawn/architect', async (req, res) => { - if (!spawnReader) { - return res.status(503).json({ - success: false, - error: 'Spawner not enabled. Start dashboard with enableSpawner: true', - }); - } - - const { cli = 'claude' } = req.body; - - const activeWorkers = spawnReader.getActiveWorkers() || []; - if (activeWorkers.some((w) => w.name.toLowerCase() === 'architect')) { - return res.status(409).json({ - success: false, - error: 'Architect agent already running', - }); - } - - const bridgeStatePath = path.join(dataDir, 'bridge-state.json'); - let projectContext = 'No bridge projects connected.'; - - if (fs.existsSync(bridgeStatePath)) { - try { - const bridgeState = JSON.parse(fs.readFileSync(bridgeStatePath, 'utf-8')); - if (bridgeState.projects && bridgeState.projects.length > 0) { - projectContext = bridgeState.projects - .map((p: { id: string; path: string; name?: string; lead?: { name: string } }) => - `- ${p.id}: ${p.path} (Lead: ${p.lead?.name || 'none'})` - ) - .join('\n'); - } - } catch (e) { - console.error('[api] Failed to read bridge state:', e); - } - } - - const outboxPath = getAgentOutboxTemplate().replace(/\$/g, '\\$'); - const architectPrompt = `You are the Architect, a cross-project coordinator overseeing multiple codebases. - -## Connected Projects -${projectContext} - -## Your Role -- Coordinate high-level work across all projects -- Assign tasks to project leads -- Ensure consistency and resolve cross-project dependencies -- Review overall architecture decisions - -## Cross-Project Messaging - -Write a file to your outbox, then output the trigger. Use project:AgentName syntax for cross-project messages: - -\`\`\`bash -# Message specific agent in a project -cat > ${outboxPath}/msg << 'EOF' -TO: project-id:AgentName - -Your message to this agent. -EOF -\`\`\` -Then output: \`->relay-file:msg\` - -\`\`\`bash -# Broadcast to all agents in a project -cat > ${outboxPath}/broadcast << 'EOF' -TO: project-id:* - -Broadcast to all agents in a project. -EOF -\`\`\` -Then output: \`->relay-file:broadcast\` - -\`\`\`bash -# Broadcast to ALL agents in ALL projects -cat > ${outboxPath}/all << 'EOF' -TO: *:* - -Broadcast to ALL agents in ALL projects. -EOF -\`\`\` -Then output: \`->relay-file:all\` - -## Getting Started -1. Check in with each project lead to understand current status -2. Identify cross-project dependencies -3. Coordinate work across teams - -Start by greeting the project leads and asking for status updates.`; - - try { - const result = await relayAdapter.spawn({ - name: 'Architect', - cli, - task: architectPrompt, - includeWorkflowConventions: true, - }); - - if (result.success) { - broadcastData().catch(() => {}); - } - - return res.json(result); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Architect spawn error:', err); - return res.status(500).json({ - success: false, - name: 'Architect', - error: message, - }); - } - }); - - // GET /api/spawned - List active spawned agents (broker pass-through). - app.get('/api/spawned', async (req, res) => { - try { - const response = await fetchBrokerSpawnedAgents({ - query: new URLSearchParams(req.query as Record), - headers: { - workspaceId: resolveWorkspaceId(req), - authorization: req.headers.authorization, - }, - }); - const body = await response.text(); - const contentType = response.headers.get('content-type') ?? 'application/json; charset=utf-8'; - return res.status(response.status).setHeader('content-type', contentType).send(body); - } catch (err) { - return res.status(502).json({ - success: false, - error: 'Broker spawned-agents proxy failed', - message: (err as Error).message, - }); - } - }); - - // DELETE /api/spawned/:name - Release a spawned agent. - app.delete('/api/spawned/:name', async (req, res) => { - if (!spawnReader) { - return res.status(503).json({ - success: false, - error: 'Spawner not enabled', - }); - } - - const { name } = req.params; - - try { - const result = await relayAdapter.release(name); - const released = result.success; - - if (released) { - agentCwdMap.delete(name); - broadcastData().catch(() => {}); - broadcastPresence({ - type: 'agent_released', - agent: { name }, - releasedBy: 'Dashboard', - timestamp: new Date().toISOString(), - }); - } - - return res.json({ - success: released, - name, - error: released ? undefined : `Agent ${name} not found`, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Release error:', err); - return res.status(500).json({ - success: false, - name, - error: message, - }); - } - }); - - // POST /api/agents/by-name/:name/interrupt - Send ESC sequence to interrupt an agent. - app.post('/api/agents/by-name/:name/interrupt', async (req, res) => { - if (!spawnReader) { - return res.status(503).json({ - success: false, - error: 'Spawner not enabled', - }); - } - - const { name } = req.params; - - if (!spawnReader.hasWorker(name)) { - return res.status(404).json({ - success: false, - error: `Agent ${name} not found or not spawned`, - }); - } - - try { - const success = await spawnReader.sendWorkerInput(name, '\x1b\x1b'); - - if (success) { - console.log(`[api] Sent interrupt (ESC ESC) to agent ${name}`); - return res.json({ - success: true, - message: `Interrupt signal sent to ${name}`, - }); - } - - return res.status(500).json({ - success: false, - error: `Failed to send interrupt to ${name}`, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Interrupt error:', err); - return res.status(500).json({ - success: false, - error: message, - }); - } - }); - - // POST /api/agents/by-name/:name/inject - Send text input to inject into agent PTY. - app.post('/api/agents/by-name/:name/inject', async (req, res) => { - const { name } = req.params; - const { text } = req.body || {}; - - if (!text || typeof text !== 'string') { - return res.status(400).json({ error: 'text is required' }); - } - - try { - if (spawnReader && typeof (spawnReader as any).sendWorkerInput === 'function') { - await (spawnReader as any).sendWorkerInput(name, text); - } - return res.json({ success: true, name, injected: text }); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : `${err}`; - return res.status(500).json({ error: `Failed to inject into ${name}: ${errorMessage}` }); - } - }); - - // GET /api/trajectory - Get current trajectory status. - app.get('/api/trajectory', async (_req, res) => { - try { - const status = await getTrajectoryStatus(); - return res.json({ - success: true, - ...status, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Trajectory status error:', err); - return res.status(500).json({ - success: false, - error: message, - }); - } - }); - - // GET /api/trajectory/steps - List trajectory steps. - app.get('/api/trajectory/steps', async (req, res) => { - try { - const trajectoryId = typeof req.query.trajectoryId === 'string' ? req.query.trajectoryId : undefined; - const result = await listTrajectorySteps(trajectoryId); - - if (result.success) { - return res.json({ - success: true, - steps: result.steps, - }); - } - - return res.status(500).json({ - success: false, - steps: [], - error: result.error, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Trajectory steps error:', err); - return res.status(500).json({ - success: false, - steps: [], - error: message, - }); - } - }); - - // GET /api/trajectory/history - List all trajectories (completed and active). - app.get('/api/trajectory/history', async (_req, res) => { - try { - const result = await getTrajectoryHistory(); - - if (result.success) { - return res.json({ - success: true, - trajectories: result.trajectories, - }); - } - - return res.status(500).json({ - success: false, - trajectories: [], - error: result.error, - }); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - console.error('[api] Trajectory history error:', err); - return res.status(500).json({ - success: false, - trajectories: [], - error: message, - }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/system.ts b/packages/dashboard-server/src/routes/system.ts deleted file mode 100644 index 3137f8b..0000000 --- a/packages/dashboard-server/src/routes/system.ts +++ /dev/null @@ -1,231 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { execFile } from 'child_process'; -import type { Application } from 'express'; -import { fetchBrokerHealth } from '../services/health-worker-manager.js'; -import { searchFiles } from '../lib/file-search.js'; - -interface RelayClientLike { - sendMessage: ( - to: string, - body: string, - kind?: string, - data?: unknown, - thread?: string - ) => string | boolean | Promise; -} - -export interface SystemRouteDeps { - dataDir: string; - teamDir: string; - projectRoot?: string; - resolveWorkspaceId: (req: { - query?: Record; - body?: Record; - headers?: Record; - }) => string | undefined; - getRelayClient: (senderName?: string, entityType?: 'agent' | 'user') => Promise; -} - -/** - * Health, files, bridge, and operational helper routes. - */ -export function registerSystemRoutes(app: Application, deps: SystemRouteDeps): void { - const { - dataDir, - teamDir, - projectRoot, - resolveWorkspaceId, - getRelayClient, - } = deps; - - app.get('/health', async (req, res) => { - try { - const workspaceId = resolveWorkspaceId(req); - const response = await fetchBrokerHealth({ - request: { - workspaceId, - authorization: req.headers.authorization, - }, - }); - const body = await response.text(); - const contentType = response.headers.get('content-type') ?? 'application/json; charset=utf-8'; - return res.status(response.status).setHeader('content-type', contentType).send(body); - } catch (err) { - return res.status(502).json({ - status: 'error', - error: 'Broker health proxy failed', - message: (err as Error).message, - }); - } - }); - - app.get('/api/health', async (req, res) => { - try { - const workspaceId = resolveWorkspaceId(req); - const response = await fetchBrokerHealth({ - request: { - workspaceId, - authorization: req.headers.authorization, - }, - }); - const body = await response.text(); - const contentType = response.headers.get('content-type') ?? 'application/json; charset=utf-8'; - return res.status(response.status).setHeader('content-type', contentType).send(body); - } catch (err) { - return res.status(502).json({ - status: 'error', - error: 'Broker health proxy failed', - message: (err as Error).message, - }); - } - }); - - app.get('/keep-alive', (_req, res) => { - let activeAgents = 0; - const agentsPath = path.join(teamDir, 'agents.json'); - if (fs.existsSync(agentsPath)) { - try { - const data = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')); - const thirtySecondsAgo = Date.now() - 30 * 1000; - activeAgents = (data.agents || []).filter((a: { lastSeen?: string }) => { - if (!a.lastSeen) return false; - return new Date(a.lastSeen).getTime() > thirtySecondsAgo; - }).length; - } catch { - // Ignore parse errors. - } - } - - return res.json({ - ok: true, - activeAgents, - timestamp: Date.now(), - }); - }); - - app.get('/api/files', async (req, res) => { - const query = (req.query.q as string) || ''; - const limit = Math.min(parseInt(req.query.limit as string, 10) || 15, 50); - const searchRoot = projectRoot || path.dirname(dataDir); - - try { - const results = await searchFiles(searchRoot, query, limit); - return res.json({ files: results, query, searchRoot: path.basename(searchRoot) }); - } catch (err) { - console.error('[api] File search error:', err); - return res.status(500).json({ error: 'Failed to search files', files: [] }); - } - }); - - app.get('/api/bridge', async (_req, res) => { - try { - const bridgeStatePath = path.join(dataDir, 'bridge-state.json'); - if (fs.existsSync(bridgeStatePath)) { - const bridgeData = JSON.parse(fs.readFileSync(bridgeStatePath, 'utf-8')); - return res.json(bridgeData); - } - - return res.json({ - projects: [], - messages: [], - connected: false, - }); - } catch (err) { - console.error('Failed to fetch bridge data', err); - return res.status(500).json({ error: 'Failed to load bridge data' }); - } - }); - - app.post('/api/beads', async (req, res) => { - const { title, assignee, priority, type } = req.body; - - if (!title || typeof title !== 'string' || !title.trim()) { - return res.status(400).json({ success: false, error: 'Title is required' }); - } - - const args: string[] = ['create', '--title', title.trim()]; - - if (assignee !== undefined && assignee !== null) { - if (typeof assignee !== 'string' || !assignee.trim()) { - return res.status(400).json({ success: false, error: 'assignee must be a non-empty string' }); - } - args.push('--assignee', assignee.trim()); - } - - if (priority !== undefined && priority !== null) { - if (typeof priority !== 'string' && typeof priority !== 'number') { - return res.status(400).json({ success: false, error: 'priority must be a string or number' }); - } - args.push('--priority', String(priority)); - } - - if (typeof type === 'string' && ['task', 'bug', 'feature'].includes(type)) { - args.push('--type', type); - } - - console.log('[api/beads] Creating bead via bd CLI'); - - execFile('bd', args, { cwd: dataDir }, (error, stdout, stderr) => { - if (error) { - console.error('[api/beads] bd create failed:', stderr || error.message); - return res.status(500).json({ - success: false, - error: stderr || error.message || 'Failed to create bead', - }); - } - - const output = stdout.trim(); - const idMatch = output.match(/Created\s+(beads-\w+)/i) || output.match(/(beads-\w+)/); - const beadId = idMatch ? idMatch[1] : `beads-${Date.now()}`; - - return res.json({ - success: true, - bead: { - id: beadId, - title: title.trim(), - assignee, - priority, - type: type || 'task', - }, - }); - }); - }); - - app.post('/api/relay/send', async (req, res) => { - const { to, content, thread } = req.body; - - if (!to || typeof to !== 'string') { - return res.status(400).json({ success: false, error: 'Recipient (to) is required' }); - } - if (!content || typeof content !== 'string') { - return res.status(400).json({ success: false, error: 'Message content is required' }); - } - - try { - const client = await getRelayClient('Dashboard'); - if (!client) { - return res.status(503).json({ - success: false, - error: 'Relay client not available', - }); - } - - const sendResult = await client.sendMessage(to, content, 'message', undefined, thread); - const messageId = typeof sendResult === 'string' && sendResult.trim() - ? sendResult - : `msg-${Date.now()}`; - - return res.json({ - success: true, - messageId, - }); - } catch (err) { - console.error('[api/relay/send] Failed to send message:', err); - return res.status(500).json({ - success: false, - error: err instanceof Error ? err.message : 'Failed to send message', - }); - } - }); -} diff --git a/packages/dashboard-server/src/routes/tasks.ts b/packages/dashboard-server/src/routes/tasks.ts deleted file mode 100644 index 425cdf1..0000000 --- a/packages/dashboard-server/src/routes/tasks.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type { Application } from 'express'; - -export interface TaskAssignment { - id: string; - agentName: string; - title: string; - description: string; - priority: 'low' | 'medium' | 'high' | 'critical'; - status: 'pending' | 'assigned' | 'in_progress' | 'completed' | 'failed'; - createdAt: string; - assignedAt?: string; - completedAt?: string; - result?: string; -} - -interface RelayClientLike { - sendMessage: ( - to: string, - body: string, - kind?: string, - data?: unknown, - thread?: string - ) => boolean; -} - -export interface TasksRouteDeps { - tasks: Map; - getRelayClient: (senderName?: string, entityType?: 'agent' | 'user') => Promise; - broadcastData: () => Promise; -} - -/** - * Task assignment and lifecycle routes. - */ -export function registerTasksRoutes(app: Application, deps: TasksRouteDeps): void { - const { tasks, getRelayClient, broadcastData } = deps; - - // GET /api/tasks - List all tasks. - app.get('/api/tasks', (req, res) => { - const status = typeof req.query.status === 'string' ? req.query.status : undefined; - const agentName = typeof req.query.agent === 'string' ? req.query.agent : undefined; - - let allTasks = Array.from(tasks.values()); - - if (status) { - allTasks = allTasks.filter((t) => t.status === status); - } - if (agentName) { - allTasks = allTasks.filter((t) => t.agentName === agentName); - } - - allTasks.sort((a, b) => { - const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; - const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; - if (priorityDiff !== 0) return priorityDiff; - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); - }); - - return res.json({ success: true, tasks: allTasks }); - }); - - // POST /api/tasks - Create and assign a task. - app.post('/api/tasks', async (req, res) => { - const { agentName, title, description, priority } = req.body; - - if (!agentName || !title || !priority) { - return res.status(400).json({ - success: false, - error: 'Missing required fields: agentName, title, priority', - }); - } - - const task: TaskAssignment = { - id: `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, - agentName, - title, - description: description || '', - priority, - status: 'assigned', - createdAt: new Date().toISOString(), - assignedAt: new Date().toISOString(), - }; - - tasks.set(task.id, task); - - try { - const client = await getRelayClient('Dashboard'); - if (client) { - const taskMessage = `TASK ASSIGNED [${priority.toUpperCase()}]: ${title}\n\n${description || 'No additional details.'}`; - await client.sendMessage(agentName, taskMessage, 'message'); - } - } catch (err) { - console.warn('[api] Could not send task to agent:', err); - } - - broadcastData().catch(() => {}); - return res.json({ success: true, task }); - }); - - // PATCH /api/tasks/:id - Update task status. - app.patch('/api/tasks/:id', (req, res) => { - const { id } = req.params; - const { status, result } = req.body; - - const task = tasks.get(id); - if (!task) { - return res.status(404).json({ success: false, error: 'Task not found' }); - } - - if (status) { - task.status = status; - if (status === 'completed' || status === 'failed') { - task.completedAt = new Date().toISOString(); - } - } - if (result !== undefined) { - task.result = result; - } - - tasks.set(id, task); - broadcastData().catch(() => {}); - return res.json({ success: true, task }); - }); - - // DELETE /api/tasks/:id - Cancel/delete a task. - app.delete('/api/tasks/:id', async (req, res) => { - const { id } = req.params; - - const task = tasks.get(id); - if (!task) { - return res.status(404).json({ success: false, error: 'Task not found' }); - } - - if (task.status === 'pending' || task.status === 'assigned' || task.status === 'in_progress') { - try { - const client = await getRelayClient('Dashboard'); - if (client) { - await client.sendMessage(task.agentName, `TASK CANCELLED: ${task.title}`, 'message'); - } - } catch (err) { - console.warn('[api] Could not send task cancellation to agent:', err); - } - } - - tasks.delete(id); - broadcastData().catch(() => {}); - return res.json({ success: true, message: 'Task cancelled' }); - }); -} diff --git a/packages/dashboard-server/src/routes/ui.ts b/packages/dashboard-server/src/routes/ui.ts deleted file mode 100644 index 18b236c..0000000 --- a/packages/dashboard-server/src/routes/ui.ts +++ /dev/null @@ -1,134 +0,0 @@ -import express, { type Application, type Response } from 'express'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -function findDashboardDir(): string | null { - const currentFileDir = path.dirname(fileURLToPath(import.meta.url)); - const isBundled = currentFileDir.startsWith('/$bunfs/'); - - if (!isBundled) { - const bundledOutDir = path.join(currentFileDir, '..', 'out'); - if (fs.existsSync(bundledOutDir)) { - return bundledOutDir; - } - } - - const cachedDashboardDir = path.join(os.homedir(), '.relay', 'dashboard', 'out'); - if (fs.existsSync(cachedDashboardDir)) { - return cachedDashboardDir; - } - - try { - const dashboardPkg = require.resolve('@agent-relay/dashboard/package.json'); - const dashboardRoot = path.dirname(dashboardPkg); - const outDir = path.join(dashboardRoot, 'out'); - if (fs.existsSync(outDir)) { - return outDir; - } - } catch { - // Package not found. - } - - return null; -} - -/** - * Register dashboard UI/static routes for integrated mode. - */ -export function registerUiRoutes(app: Application): void { - const dashboardDir = findDashboardDir(); - if (dashboardDir) { - console.log(`[dashboard] Serving from: ${dashboardDir}`); - app.use(express.static(dashboardDir, { extensions: ['html'] })); - - const uiMissingMessage = - 'Dashboard UI file not found. Please reinstall using: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash'; - - const sendFileOr = ( - res: Response, - filePath: string, - onError: (err: Error) => void, - ) => { - res.sendFile(filePath, (err) => { - if (err && !res.headersSent) { - onError(err); - } - }); - }; - - const sendFileOrRedirectRoot = (res: Response, filePath: string) => { - sendFileOr(res, filePath, () => { - if (fs.existsSync(path.join(dashboardDir, 'index.html'))) { - res.redirect(302, '/'); - return; - } - res.status(404).send(uiMissingMessage); - }); - }; - - const resolveMetricsFilePath = () => { - const candidates = [ - path.join(dashboardDir, 'metrics.html'), - path.join(dashboardDir, 'metrics', 'index.html'), - path.join(dashboardDir, 'app.html'), - path.join(dashboardDir, 'index.html'), - ]; - - return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0]; - }; - - app.get('/metrics', (_req, res) => { - sendFileOrRedirectRoot(res, resolveMetricsFilePath()); - }); - app.get('/app', (_req, res) => { - sendFileOrRedirectRoot(res, path.join(dashboardDir, 'app.html')); - }); - app.get('/app/{*path}', (_req, res) => { - sendFileOrRedirectRoot(res, path.join(dashboardDir, 'app.html')); - }); - return; - } - - const fallbackHtml = ` - - - Agent Relay Dashboard - - - -

Agent Relay Dashboard

-

The dashboard API is running, but the UI files are not available.

-

To get the full dashboard UI, reinstall using the official installer:

-
curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash
-
- API Status: Running
- View connected agents | - View messages -
- -`; - - app.get('/', (_req, res) => { - res.type('html').send(fallbackHtml); - }); - - app.get('/metrics', (_req, res) => { - res.type('html').send(fallbackHtml); - }); - - app.get('/app', (_req, res) => { - res.type('html').send(fallbackHtml); - }); - - app.get('/app/{*path}', (_req, res) => { - res.type('html').send(fallbackHtml); - }); -} diff --git a/packages/dashboard-server/src/server.ts b/packages/dashboard-server/src/server.ts deleted file mode 100644 index d3894bb..0000000 --- a/packages/dashboard-server/src/server.ts +++ /dev/null @@ -1,600 +0,0 @@ -import express from 'express'; -import { WebSocketServer, WebSocket } from 'ws'; -import http from 'http'; -import path from 'path'; -import fs from 'fs'; -import { createStorageAdapter, type StorageAdapter } from '@agent-relay/storage/adapter'; -import { UserBridge } from './services/user-bridge.js'; -import { detectWorkspacePath } from '@agent-relay/config'; -import { BrokerSpawnReader } from './services/broker-spawn-reader.js'; -import type { RelayAdapter } from '@agent-relay/sdk'; - -import type { DashboardOptions } from './types/index.js'; -import { - startCLIAuth, - getAuthSession, - cancelAuthSession, - submitAuthCode, - completeAuthSession, - getSupportedProviders, -} from './lib/cli-auth.js'; -import { createServerState, type BrokerRelayClientShim } from './lib/server-state.js'; -import { createBroadcasters } from './lib/broadcast.js'; -import { createDataAssembly } from './lib/data-assembly.js'; -import { createChannelPersistence } from './lib/channel-state.js'; -import { setupWebSocketRuntime } from './lib/websocket-runtime.js'; -import { initializeAttachmentStorage } from './lib/attachment-storage.js'; -import { setupMainWebSocket } from './websocket/main.js'; -import { setupBridgeWebSocket } from './websocket/bridge.js'; -import { setupLogsWebSocket } from './websocket/logs.js'; -import { setupPresenceWebSocket } from './websocket/presence.js'; -import { registerMessagingRoutes } from './routes/messaging.js'; -import { registerHistoryRoutes } from './routes/history.js'; -import { registerAuthRoutes } from './routes/auth.js'; -import { registerSettingsRoutes } from './routes/settings.js'; -import { registerDecisionsRoutes, type Decision } from './routes/decisions.js'; -import { registerTasksRoutes, type TaskAssignment } from './routes/tasks.js'; -import { registerFleetRoutes } from './routes/fleet.js'; -import { registerSpawnRoutes } from './routes/spawn.js'; -import { registerMetricsRoutes } from './routes/metrics.js'; -import { registerChannelsIntegratedRoutes } from './routes/channels-integrated.js'; -import { registerSystemRoutes } from './routes/system.js'; -import { registerUiRoutes } from './routes/ui.js'; -import { registerModelsRoutes } from './routes/models.js'; -import { - getBindHost, - isAgentOnline as checkAgentOnline, - isRecipientOnline as checkRecipientOnline, - getTeamMembers as resolveTeamMembers, - isValidUsername, - isValidAvatarUrl, -} from './lib/utils.js'; - -interface Attachment { - id: string; - filename: string; - mimeType: string; - size: number; - url: string; - /** Absolute file path for agents to read the file directly */ - filePath?: string; - width?: number; - height?: number; - data?: string; -} - -export async function startDashboard(port: number, dataDir: string, teamDir: string, dbPath?: string): Promise; -export async function startDashboard(options: DashboardOptions): Promise; -export async function startDashboard( - portOrOptions: number | DashboardOptions, - dataDirArg?: string, - teamDirArg?: string, - dbPathArg?: string -): Promise { - // Handle overloaded signatures - const options: DashboardOptions = typeof portOrOptions === 'number' - ? { port: portOrOptions, dataDir: dataDirArg!, teamDir: teamDirArg!, dbPath: dbPathArg } - : portOrOptions; - - const { port, dataDir, teamDir, dbPath, enableSpawner, projectRoot, tmuxSession, onMarkSpawning, onClearSpawning, verbose } = options; - let { relayAdapter } = options; - - // Auto-create a RelayAdapter when projectRoot is available and no adapter is passed. - // This makes the dashboard self-contained — `relay-dashboard-server --integrated --project-root .` - // works without the CLI. - if (!relayAdapter && enableSpawner && projectRoot) { - try { - const brokerSdk = await import('@agent-relay/sdk'); - relayAdapter = new brokerSdk.RelayAdapter({ - cwd: projectRoot, - clientName: 'dashboard', - }); - console.log('[dashboard] Auto-created RelayAdapter for broker mode'); - } catch { - // Ignore import failures here and throw a clear error below. - } - } - - if (!relayAdapter) { - throw new Error('[dashboard] RelayAdapter is required (legacy daemon relay-client mode has been removed)'); - } - - // Debug logging helper - only logs when verbose is true or VERBOSE env var is set - const isVerbose = verbose || process.env.VERBOSE === 'true'; - const debug = (message: string) => { - if (isVerbose) { - console.log(message); - } - }; - - console.log('[dashboard] Starting dashboard...'); - - const disableStorage = process.env.RELAY_DISABLE_STORAGE === 'true'; - // Use createStorageAdapter to match daemon's storage type (JSONL by default) - // This ensures dashboard reads from the same storage as daemon writes to - // Enable watchForChanges so JSONL adapter auto-reloads when daemon writes new messages - const storagePath = dbPath ?? path.join(dataDir, 'messages.sqlite'); - const storage: StorageAdapter | undefined = disableStorage - ? undefined - : await createStorageAdapter(storagePath, { watchForChanges: true }); - - const defaultWorkspaceId = process.env.RELAY_WORKSPACE_ID ?? process.env.AGENT_RELAY_WORKSPACE_ID; - - const { - loadChannelRecords, - loadPersistedChannelsForUser, - persistChannelMembershipEvent, - } = createChannelPersistence({ - storage, - defaultWorkspaceId, - }); - - // Initialize spawner if enabled - // When projectRoot is explicitly provided (e.g., via --project-root), use it directly. - // Only use detectWorkspacePath for cloud workspace auto-detection when no explicit root is given. - // This fixes #380: detectWorkspacePath could re-resolve projectRoot incorrectly when - // tool directories like ~/.nvm contain package.json markers. - const workspacePath = projectRoot || detectWorkspacePath(dataDir); - console.log(`[dashboard] Workspace path: ${workspacePath}`); - - // SpawnReader is now always broker-backed. - const brokerSpawnReader = new BrokerSpawnReader(relayAdapter); - await relayAdapter.start(); - await brokerSpawnReader.initialize(); - const spawnReader = brokerSpawnReader; - console.log('[dashboard] Using broker adapter for spawn operations'); - - process.on('uncaughtException', (err) => { - console.error('Uncaught Exception:', err); - }); - - process.on('unhandledRejection', (reason, promise) => { - console.error('Unhandled Rejection at:', promise, 'reason:', reason); - }); - - const app = express(); - const server = http.createServer(app); - - // Use noServer mode to manually route upgrade requests - // This prevents the bug where multiple WebSocketServers attached to the same - // HTTP server cause conflicts - each one's upgrade handler fires and the ones - // that don't match the path call abortHandshake(400), writing raw HTTP to the socket - const wss = new WebSocketServer({ - noServer: true, - perMessageDeflate: false, - skipUTF8Validation: true, - maxPayload: 100 * 1024 * 1024 // 100MB - }); - const wssBridge = new WebSocketServer({ - noServer: true, - perMessageDeflate: false, - skipUTF8Validation: true, - maxPayload: 100 * 1024 * 1024 - }); - const wssLogs = new WebSocketServer({ - noServer: true, - perMessageDeflate: false, - skipUTF8Validation: true, - maxPayload: 100 * 1024 * 1024 - }); - const wssPresence = new WebSocketServer({ - noServer: true, - perMessageDeflate: false, - skipUTF8Validation: true, - maxPayload: 1024 * 1024 // 1MB - presence messages are small - }); - - // Track online users for presence with multi-tab support - // username -> { connections: Set, userInfo } - interface UserPresenceInfo { - username: string; - avatarUrl?: string; - connectedAt: string; - lastSeen: string; - } - interface UserPresenceState { - info: UserPresenceInfo; - connections: Set; - } - - const state = createServerState({ - dataDir, - teamDir, - projectRoot, - verbose: isVerbose, - defaultWorkspaceId, - storage, - relayAdapter, - spawnReader, - wss, - wssBridge, - wssLogs, - wssPresence, - }); - const logSubscriptions = state.logSubscriptions; - const fileWatchers = state.fileWatchers; - const fileLastSize = state.fileLastSize; - const mainMessageBuffer = state.mainMessageBuffer; - const agentLogBuffers = state.agentLogBuffers; - const getAgentLogBuffer = state.getAgentLogBuffer; - const mainClientAlive = state.mainClientAlive; - const bridgeClientAlive = state.bridgeClientAlive; - const onlineUsers = state.onlineUsers as Map; - const agentCwdMap = state.agentCwdMap; - const resolveWorkspaceId = state.resolveWorkspaceId; - - setupWebSocketRuntime({ - server, - wss, - wssBridge, - wssLogs, - wssPresence, - mainClientAlive, - bridgeClientAlive, - debug, - }); - - if (storage) { - await storage.init(); - } - - // Request logger for debugging - app.use((req, res, next) => { - if (req.path.startsWith('/api/channels')) { - console.log(`[dashboard] ${req.method} ${req.path} - incoming request`); - } - next(); - }); - - // Increase JSON body limit for base64 image uploads (10MB) - app.use(express.json({ limit: '10mb' })); - - const { attachmentsDir, uploadsDir, stopEviction } = initializeAttachmentStorage(dataDir); - process.on('beforeExit', stopEviction); - - // Serve uploaded files statically - app.use('/uploads', express.static(uploadsDir)); - // Serve attachments from ~/.relay/attachments - app.use('/attachments', express.static(attachmentsDir)); - - // In-memory attachment registry (for current session) - // Attachments are also stored on disk, so this is just for quick lookups - const attachmentRegistry = state.attachmentRegistry as Map; - - registerUiRoutes(app); - - // Relay clients for sending messages from dashboard are broker-backed shims. - // Forward declaration - initialized after helper factories. - let userBridge: UserBridge | undefined; - - const getRelayClient = async ( - senderName: string = 'Dashboard', - _entityType?: 'agent' | 'user' - ): Promise => { - return state.getBrokerClientShim(senderName); - }; - - userBridge = new UserBridge({ - createRelayClient: async (options: { - agentName: string; - entityType: 'user'; - displayName?: string; - avatarUrl?: string; - }) => { - return getRelayClient(options.agentName, options.entityType); - }, - loadPersistedChannels: (username: string) => - loadPersistedChannelsForUser(username, defaultWorkspaceId), - lookupUserInfo: (username: string) => { - const presence = onlineUsers.get(username); - if (presence) { - return { avatarUrl: presence.info.avatarUrl }; - } - return undefined; - }, - }); - - const isAgentOnline = (agentName: string): boolean => checkAgentOnline(teamDir, agentName); - const isRecipientOnline = (name: string): boolean => checkRecipientOnline(teamDir, name, onlineUsers, userBridge); - const getTeamMembers = (teamName: string): string[] => - resolveTeamMembers(teamName, projectRoot, dataDir, teamDir, spawnReader); - - const { - getAllData, - getBridgeData, - isInternalAgent, - remapAgentName, - buildThreadSummaryMap, - formatDuration, - } = createDataAssembly({ - dataDir, - teamDir, - projectRoot, - defaultWorkspaceId, - storage, - spawnReader, - onlineUsers, - agentCwdMap, - debug, - }); - - registerHistoryRoutes(app, { - storage, - formatDuration, - isInternalAgent, - remapAgentName, - }); - - // Track clients that are still initializing (haven't received first data yet) - // This prevents race conditions where broadcastData sends before initial data is sent - const initializingClients = state.initializingClients; - - const { broadcastData, broadcastBridgeData, broadcastPresence, broadcastLogOutput } = - createBroadcasters(state, { getAllData, getBridgeData, debug }); - - registerMessagingRoutes(app, { - getTeamMembers, - isAgentOnline, - isRecipientOnline, - getRelayClient, - attachmentRegistry, - attachmentsDir, - broadcastData, - storage, - remapAgentName, - }); - - // Expose broadcastLogOutput for PTY wrappers to call. - (global as any).__broadcastLogOutput = broadcastLogOutput; - - // Handle new WebSocket connections - send initial data immediately. - setupMainWebSocket({ - wss, - mainClientAlive, - mainMessageBuffer, - initializingClients, - getAllData, - debug, - }); - - // Handle bridge WebSocket connections. - setupBridgeWebSocket({ - wssBridge, - bridgeClientAlive, - getBridgeData, - debug, - }); - - setupLogsWebSocket({ - wssLogs, - teamDir, - debug, - logSubscriptions, - fileWatchers, - fileLastSize, - agentLogBuffers, - getAgentLogBuffer, - spawnReader, - }); - - // ===== Presence WebSocket Handler ===== - const { broadcastChannelMessage, broadcastDirectMessage } = setupPresenceWebSocket({ - wss, - wssPresence, - mainMessageBuffer, - onlineUsers, - presenceHealth: state.presenceHealth, - broadcastPresence, - isValidUsername, - isValidAvatarUrl, - getUserBridge: () => userBridge, - debug, - }); - - relayAdapter.onEvent((event) => { - if (event.kind === 'relay_inbound') { - const senderPresence = onlineUsers.get(event.from); - const fromAvatarUrl = senderPresence?.info.avatarUrl; - const fromEntityType: 'user' | 'agent' = senderPresence ? 'user' : 'agent'; - const timestamp = new Date().toISOString(); - - // Route to channel or direct message based on target. - if (event.target.startsWith('#')) { - broadcastChannelMessage({ - type: 'channel_message', - targetUser: event.target, - channel: event.target, - from: event.from, - fromAvatarUrl, - fromEntityType, - body: event.body, - thread: event.thread_id, - timestamp, - }); - } else { - broadcastDirectMessage({ - type: 'direct_message', - targetUser: event.target, - from: event.from, - fromAvatarUrl, - fromEntityType, - body: event.body, - id: event.event_id, - messageId: event.event_id, - timestamp, - }); - } - } - }); - console.log('[dashboard] Broker event subscription active for message forwarding'); - - registerChannelsIntegratedRoutes(app, { - storage, - teamDir, - attachmentRegistry, - onlineUsers, - userBridge, - spawnReader, - resolveWorkspaceId, - loadChannelRecords, - persistChannelMembershipEvent, - getRelayClient, - buildThreadSummaryMap, - isInternalAgent, - getAllData, - }); - - registerAuthRoutes(app, { - startCLIAuth, - getAuthSession, - cancelAuthSession, - submitAuthCode, - completeAuthSession, - getSupportedProviders, - }); - - registerMetricsRoutes(app, { - teamDir, - spawnReader, - resolveWorkspaceId, - }); - registerSystemRoutes(app, { - dataDir, - teamDir, - projectRoot, - resolveWorkspaceId, - getRelayClient, - }); - - registerSpawnRoutes(app, { - dataDir, - projectRoot, - relayAdapter, - spawnReader, - agentCwdMap, - isAgentOnline, - resolveWorkspaceId, - broadcastData, - broadcastPresence, - }); - - registerSettingsRoutes(app); - registerModelsRoutes(app); - - const decisions = state.decisions as Map; - registerDecisionsRoutes(app, { - decisions, - getRelayClient, - broadcastData, - }); - - const tasks = state.tasks as Map; - registerTasksRoutes(app, { - tasks, - getRelayClient, - broadcastData, - }); - registerFleetRoutes(app, { - dataDir, - spawnReader, - wss, - decisions, - tasks, - }); - - // Watch for changes - poll as a safety net for DB-backed storage mode. - // Real-time updates are already handled by explicit broadcastData() calls - // at every data mutation point (message send, spawn, release, cwd update, etc.). - // This interval only catches external/indirect changes (presence, DB edits). - if (storage) { - setInterval(() => { - broadcastData().catch((err) => console.error('Broadcast failed', err)); - broadcastBridgeData().catch((err) => console.error('Bridge broadcast failed', err)); - }, 5000); - } else { - let fsWait: NodeJS.Timeout | null = null; - let bridgeFsWait: NodeJS.Timeout | null = null; - try { - if (fs.existsSync(dataDir)) { - console.log(`Watching ${dataDir} for changes...`); - fs.watch(dataDir, { recursive: true }, (eventType, filename) => { - if (filename && (filename.endsWith('inbox.md') || filename.endsWith('team.json') || filename.endsWith('agents.json') || filename.endsWith('processing-state.json'))) { - // Debounce - if (fsWait) return; - fsWait = setTimeout(() => { - fsWait = null; - broadcastData().catch((err) => { - console.error('Broadcast failed', err); - }); - }, 100); - } - // Watch for bridge state changes - if (filename && filename.endsWith('bridge-state.json')) { - if (bridgeFsWait) return; - bridgeFsWait = setTimeout(() => { - bridgeFsWait = null; - broadcastBridgeData().catch((err) => { - console.error('Bridge broadcast failed', err); - }); - }, 100); - } - }); - } else { - console.warn(`Data directory ${dataDir} does not exist yet.`); - } - } catch (e) { - console.error('Watch failed:', e); - } - } - - // Try to find an available port, starting from the requested port - const findAvailablePort = async (startPort: number, maxAttempts = 10): Promise => { - for (let attempt = 0; attempt < maxAttempts; attempt++) { - const portToTry = startPort + attempt; - const isAvailable = await new Promise((resolve) => { - const testServer = http.createServer(); - testServer.once('error', () => resolve(false)); - testServer.once('listening', () => { - testServer.close(); - resolve(true); - }); - testServer.listen(portToTry); - }); - - if (isAvailable) { - return portToTry; - } - console.log(`Port ${portToTry} in use, trying ${portToTry + 1}...`); - } - throw new Error(`Could not find available port after trying ${startPort}-${startPort + maxAttempts - 1}`); - }; - - const availablePort = await findAvailablePort(port); - if (availablePort !== port) { - console.log(`Requested dashboard port ${port} is busy; switching to ${availablePort}.`); - } - - return new Promise((resolve, reject) => { - const host = getBindHost(); - const listenCallback = async () => { - console.log(`Dashboard running at http://${host || 'localhost'}:${availablePort} (build: cloud-channels-v2)`); - console.log(`Monitoring: ${dataDir}`); - - // Health and spawned status are now pass-through broker/cloud proxies. - - resolve(availablePort); - }; - - // Bind to specified host in cloud environments, or let Node.js default for local - if (host) { - server.listen(availablePort, host, listenCallback); - } else { - server.listen(availablePort, listenCallback); - } - - server.on('error', (err) => { - console.error('Server error:', err); - reject(err); - }); - }); -} diff --git a/packages/dashboard-server/src/services/broker-spawn-reader.ts b/packages/dashboard-server/src/services/broker-spawn-reader.ts deleted file mode 100644 index 2e146c6..0000000 --- a/packages/dashboard-server/src/services/broker-spawn-reader.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * BrokerSpawnReader — Implements SpawnManagerLike using RelayAdapter. - * - * Bridges the dashboard's sync SpawnManagerLike interface with the - * async broker SDK by maintaining an internal cache of agents and - * worker output, updated via broker event subscription. - */ - -import type { RelayAdapter, RelayAgentInfo } from '@agent-relay/sdk'; -import type { SpawnManagerLike } from '../types/index.js'; - -/** Max lines of output to buffer per agent (matches xterm scrollback). */ -const MAX_OUTPUT_LINES = 10_000; - -/** Max bytes for the raw output buffer per agent (5 MB). */ -const MAX_RAW_BYTES = 5_000_000; - -interface CachedAgent { - name: string; - cli: string; - task: string; - team?: string; - spawnerName?: string; - spawnedAt: number; - pid?: number; -} - -export class BrokerSpawnReader implements SpawnManagerLike { - private agentCache = new Map(); - private outputBuffers = new Map(); - private rawOutputBuffers = new Map(); - private unsubscribe?: () => void; - - constructor(private adapter: RelayAdapter) {} - - /** - * Start listening to broker events. - * Must be called once after adapter.start() to seed state and begin tracking. - */ - async initialize(): Promise { - // Seed cache from current agent list - try { - const agents = await this.adapter.listAgents(); - for (const a of agents) { - this.agentCache.set(a.name, { - name: a.name, - cli: a.cli || 'unknown', - task: '', - spawnedAt: Date.now(), - pid: a.pid, - }); - } - } catch { - // Non-fatal — broker may not have agents yet - } - - // Subscribe to events for live updates - this.unsubscribe = this.adapter.onEvent((event: any) => { - switch (event.kind) { - case 'agent_spawned': { - this.agentCache.set(event.name, { - name: event.name, - cli: 'unknown', - task: '', - spawnedAt: Date.now(), - }); - // Initialize output buffers - this.outputBuffers.set(event.name, []); - this.rawOutputBuffers.set(event.name, ''); - break; - } - - case 'agent_released': - case 'agent_exited': { - this.agentCache.delete(event.name); - // Keep output buffers so logs are still accessible after exit - break; - } - - case 'worker_stream': { - const lines = this.outputBuffers.get(event.name); - if (lines) { - lines.push(event.chunk); - // Trim to max buffer size - if (lines.length > MAX_OUTPUT_LINES) { - lines.splice(0, lines.length - MAX_OUTPUT_LINES); - } - } else { - this.outputBuffers.set(event.name, [event.chunk]); - } - - // Append raw chunk as-is (no newline synthesis) so TUI repaint - // control sequences remain faithful for xterm rendering. - let raw = (this.rawOutputBuffers.get(event.name) ?? '') + event.chunk; - if (raw.length > MAX_RAW_BYTES) { - // Trim from the front, keeping from the first newline after the cut point - const trimPoint = raw.length - MAX_RAW_BYTES; - const newlineIdx = raw.indexOf('\n', trimPoint); - raw = newlineIdx !== -1 ? raw.slice(newlineIdx + 1) : raw.slice(trimPoint); - } - this.rawOutputBuffers.set(event.name, raw); - break; - } - } - }); - } - - hasWorker(name: string): boolean { - return this.agentCache.has(name); - } - - getActiveWorkers(): Array<{ - name: string; - cli: string; - task: string; - team?: string; - spawnerName?: string; - spawnedAt: number; - pid?: number; - }> { - return Array.from(this.agentCache.values()); - } - - getWorkerOutput(name: string, limit = 500): string[] | undefined { - const lines = this.outputBuffers.get(name); - if (!lines) return undefined; - return lines.slice(-limit); - } - - getWorkerRawOutput(name: string): string | undefined { - return this.rawOutputBuffers.get(name); - } - - async sendWorkerInput(name: string, data: string): Promise { - try { - await this.adapter.sendInput(name, data); - return true; - } catch (err) { - console.error(`[broker-spawn-reader] Failed to send input to ${name}:`, err); - return false; - } - } - - /** Refresh the agent cache from the broker. */ - async refresh(): Promise { - try { - const agents = await this.adapter.listAgents(); - const currentNames = new Set(agents.map((a: any) => a.name)); - - // Remove agents no longer present - for (const name of this.agentCache.keys()) { - if (!currentNames.has(name)) { - this.agentCache.delete(name); - } - } - - // Add/update agents - for (const a of agents) { - const existing = this.agentCache.get(a.name); - if (existing) { - existing.pid = a.pid; - } else { - this.agentCache.set(a.name, { - name: a.name, - cli: a.cli || 'unknown', - task: '', - spawnedAt: Date.now(), - pid: a.pid, - }); - } - } - } catch { - // Non-fatal - } - } - - /** Clean up event subscriptions. */ - destroy(): void { - this.unsubscribe?.(); - this.agentCache.clear(); - this.outputBuffers.clear(); - this.rawOutputBuffers.clear(); - } -} diff --git a/packages/dashboard-server/src/services/index.ts b/packages/dashboard-server/src/services/index.ts index 31a3705..9df36da 100644 --- a/packages/dashboard-server/src/services/index.ts +++ b/packages/dashboard-server/src/services/index.ts @@ -4,7 +4,6 @@ * Re-exports all service modules for the dashboard server. */ -export { UserBridge, type IRelayClient } from './user-bridge.js'; export { fetchCloudNeedsAttention, parseNeedsAttentionAgents, diff --git a/packages/dashboard-server/src/services/user-bridge.ts b/packages/dashboard-server/src/services/user-bridge.ts deleted file mode 100644 index c271f37..0000000 --- a/packages/dashboard-server/src/services/user-bridge.ts +++ /dev/null @@ -1,546 +0,0 @@ -/** - * User Bridge - Bridges dashboard WebSocket users to the relay daemon. - * - * This module allows human users connected via WebSocket to: - * - Register as "user" entities in the relay daemon - * - Join/leave channels - * - Send/receive messages through the relay daemon - * - Communicate with agents and other users - */ - -import type { WebSocket } from 'ws'; - -/** - * Relay client interface (subset of RelayClient for dependency injection) - */ -export interface IRelayClient { - connect(): Promise; - disconnect(): void; - state: string; - sendMessage( - to: string, - body: string, - kind?: string, - data?: unknown, - thread?: string - ): boolean; - // Channel operations - joinChannel(channel: string, displayName?: string): boolean; - leaveChannel(channel: string, reason?: string): boolean; - sendChannelMessage( - channel: string, - body: string, - options?: { thread?: string; mentions?: string[]; attachments?: unknown[]; data?: Record } - ): boolean; - // Admin channel operations - adminJoinChannel?(channel: string, member: string): boolean; - adminRemoveMember?(channel: string, member: string): boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onMessage?: (from: string, payload: any, messageId: string, meta?: any, originalTo?: string) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChannelMessage?: (from: string, channel: string, body: string, envelope: any) => void; -} - -/** - * Factory function type for creating relay clients - */ -export type RelayClientFactory = (options: { - agentName: string; - entityType: 'user'; - displayName?: string; - avatarUrl?: string; -}) => Promise; - -/** - * User session state - supports multiple WebSocket connections (multi-tab) - */ -interface UserSession { - username: string; - relayClient: IRelayClient; - /** Multiple WebSocket connections for multi-tab support */ - webSockets: Set; - channels: Set; - avatarUrl?: string; -} - -/** - * User info for avatar lookups - */ -export interface UserInfo { - avatarUrl?: string; -} - -/** - * Options for creating a UserBridge - */ -export interface UserBridgeOptions { - createRelayClient: RelayClientFactory; - loadPersistedChannels?: (username: string) => Promise; - /** Optional callback to look up user info (avatar URL) by username */ - lookupUserInfo?: (username: string) => UserInfo | undefined; -} - -/** - * Message options for sending - */ -export interface SendMessageOptions { - thread?: string; - data?: Record; - attachments?: unknown[]; -} - -/** - * UserBridge manages the connection between dashboard WebSocket users - * and the relay daemon. - */ -export class UserBridge { - private readonly createRelayClient: RelayClientFactory; - private readonly loadPersistedChannels?: (username: string) => Promise; - private readonly lookupUserInfo?: (username: string) => UserInfo | undefined; - private readonly users = new Map(); - - constructor(options: UserBridgeOptions) { - this.createRelayClient = options.createRelayClient; - this.loadPersistedChannels = options.loadPersistedChannels; - this.lookupUserInfo = options.lookupUserInfo; - } - - /** - * Get the relay client for a user if they are registered. - * This allows external code to reuse the userBridge's relay client - * instead of creating a duplicate connection. - */ - getRelayClient(username: string): IRelayClient | undefined { - return this.users.get(username)?.relayClient; - } - - /** - * Register a user with the relay daemon. - * Creates a relay client connection for the user. - */ - async registerUser( - username: string, - webSocket: WebSocket, - options?: { avatarUrl?: string; displayName?: string } - ): Promise { - // If user already registered, just update the WebSocket (multi-tab support) - if (this.users.has(username)) { - console.log(`[user-bridge] User ${username} already registered, updating WebSocket`); - this.updateWebSocket(username, webSocket); - return; - } - - // Create relay client for this user - const relayClient = await this.createRelayClient({ - agentName: username, - entityType: 'user', - displayName: options?.displayName, - avatarUrl: options?.avatarUrl, - }); - - // Connect to daemon - await relayClient.connect(); - - // Set up message handler to forward direct messages to WebSocket - relayClient.onMessage = (from, payload, _messageId, _meta, _originalTo) => { - const body = typeof payload === 'object' && payload !== null && 'body' in payload - ? (payload as { body: string }).body - : String(payload); - this.handleIncomingDirectMessage(username, from, body, payload); - }; - - // Set up channel message handler to forward channel messages to WebSocket - relayClient.onChannelMessage = (from, channel, body, envelope) => { - this.handleIncomingChannelMessage(username, from, channel, body, envelope); - }; - - // Create session with WebSocket set for multi-tab support - const session: UserSession = { - username, - relayClient, - webSockets: new Set([webSocket]), - channels: new Set(), - avatarUrl: options?.avatarUrl, - }; - - this.users.set(username, session); - console.log(`[user-bridge] User registered: ${username} (total users: ${this.users.size}, connections: 1)`); - - // Auto-join user to #general channel - // Note: The daemon auto-joins on connect, but we need to track locally too - session.channels.add('#general'); - - if (this.loadPersistedChannels) { - try { - const persistedChannels = await this.loadPersistedChannels(username); - for (const channel of persistedChannels) { - if (channel === '#general') continue; - if (session.channels.has(channel)) continue; - session.relayClient.joinChannel(channel, username); - session.channels.add(channel); - } - } catch (err) { - console.error(`[user-bridge] Failed to restore persisted channels for ${username}:`, err); - } - } - - // Set up WebSocket close handler to remove this specific connection - webSocket.on('close', () => { - this.removeWebSocket(username, webSocket); - }); - - console.log(`[user-bridge] User ${username} registered with relay daemon`); - } - - /** - * Remove a specific WebSocket connection from a user's session. - * If this was the last connection, unregister the user entirely. - */ - private removeWebSocket(username: string, webSocket: WebSocket): void { - const session = this.users.get(username); - if (!session) return; - - session.webSockets.delete(webSocket); - console.log(`[user-bridge] WebSocket closed for ${username} (${session.webSockets.size} connections remaining)`); - - // Only unregister if ALL connections are closed - if (session.webSockets.size === 0) { - this.unregisterUser(username); - } - } - - /** - * Unregister a user and disconnect their relay client. - */ - unregisterUser(username: string): void { - const session = this.users.get(username); - if (!session) return; - - session.relayClient.disconnect(); - this.users.delete(username); - - console.log(`[user-bridge] User ${username} unregistered from relay daemon`); - } - - /** - * Check if a user is registered. - */ - isUserRegistered(username: string): boolean { - return this.users.has(username); - } - - /** - * Add a new WebSocket connection for an existing user session. - * This is needed when a user opens a new tab. - */ - updateWebSocket(username: string, newWebSocket: WebSocket): boolean { - const session = this.users.get(username); - if (!session) { - console.log(`[user-bridge] Cannot add WebSocket - user ${username} not registered`); - return false; - } - - // Add the new WebSocket to the set - session.webSockets.add(newWebSocket); - - // Set up close handler to remove this specific connection - newWebSocket.on('close', () => { - this.removeWebSocket(username, newWebSocket); - }); - - console.log(`[user-bridge] Added WebSocket for user ${username} (${session.webSockets.size} connections)`); - return true; - } - - /** - * Get list of all registered users. - */ - getRegisteredUsers(): string[] { - return Array.from(this.users.keys()); - } - - /** - * Join a channel. - */ - async joinChannel(username: string, channel: string): Promise { - const session = this.users.get(username); - if (!session) { - console.warn(`[user-bridge] Cannot join channel - user ${username} not registered`); - return false; - } - - // Send CHANNEL_JOIN via relay client - const success = session.relayClient.joinChannel(channel, username); - - if (success) { - // Track membership - session.channels.add(channel); - } - - return success; - } - - /** - * Leave a channel. - */ - async leaveChannel(username: string, channel: string): Promise { - const session = this.users.get(username); - if (!session) { - console.warn(`[user-bridge] Cannot leave channel - user ${username} not registered`); - return false; - } - - // Send CHANNEL_LEAVE via relay client - const success = session.relayClient.leaveChannel(channel); - - if (success) { - // Update membership - session.channels.delete(channel); - console.log(`[user-bridge] User ${username} left channel ${channel}`); - } - - return success; - } - - /** - * Get channels a user has joined. - */ - getUserChannels(username: string): string[] { - const session = this.users.get(username); - return session ? Array.from(session.channels) : []; - } - - /** - * Send a message to a channel. - */ - async sendChannelMessage( - username: string, - channel: string, - body: string, - options?: SendMessageOptions - ): Promise { - const session = this.users.get(username); - if (!session) { - console.warn(`[user-bridge] Cannot send - user ${username} not registered`); - return false; - } - - return session.relayClient.sendChannelMessage(channel, body, { - thread: options?.thread, - data: options?.data, - attachments: options?.attachments, - }); - } - - /** - * Send a direct message to another user or agent. - */ - async sendDirectMessage( - fromUsername: string, - toName: string, - body: string, - options?: SendMessageOptions - ): Promise { - const session = this.users.get(fromUsername); - if (!session) { - console.warn(`[user-bridge] Cannot send DM - user ${fromUsername} not registered`); - return false; - } - - return session.relayClient.sendMessage( - toName, - body, - 'message', - options?.data, - options?.thread - ); - } - - /** - * Handle incoming direct message from relay daemon. - */ - private handleIncomingDirectMessage( - username: string, - from: string, - body: string, - payload: unknown - ): void { - // Skip channel messages - they are handled by handleIncomingChannelMessage - // The relay client calls both onMessage and onChannelMessage for channel messages, - // with _isChannelMessage flag set in the data for onMessage calls - const payloadObj = payload as { body?: string; data?: { _isChannelMessage?: boolean } } | undefined; - if (payloadObj?.data?._isChannelMessage) { - return; // Skip - will be handled by onChannelMessage callback - } - - const session = this.users.get(username); - if (!session) return; - - // Look up sender's avatar if lookup function is available - const senderInfo = this.lookupUserInfo?.(from); - const fromAvatarUrl = senderInfo?.avatarUrl; - // Determine entity type: user if they have info, agent otherwise - const fromEntityType: 'user' | 'agent' = senderInfo ? 'user' : 'agent'; - - const message = JSON.stringify({ - type: 'direct_message', - from, - fromAvatarUrl, - fromEntityType, - body: payloadObj?.body || body, - timestamp: new Date().toISOString(), - }); - - // Send to ALL open WebSocket connections (multi-tab support) - for (const ws of session.webSockets) { - if (ws.readyState === 1) { // OPEN - ws.send(message); - } - } - } - - /** - * Handle incoming channel message from relay daemon. - */ - private handleIncomingChannelMessage( - username: string, - from: string, - channel: string, - body: string, - envelope: unknown - ): void { - const session = this.users.get(username); - if (!session) return; - - // Look up sender's avatar if lookup function is available - const senderInfo = this.lookupUserInfo?.(from); - const fromAvatarUrl = senderInfo?.avatarUrl; - // Determine entity type: user if they have info, agent otherwise - const fromEntityType: 'user' | 'agent' = senderInfo ? 'user' : 'agent'; - - // Channel message - const env = envelope as { payload?: { thread?: string; mentions?: string[] } } | undefined; - const message = JSON.stringify({ - type: 'channel_message', - channel, - from, - fromAvatarUrl, - fromEntityType, - body, - thread: env?.payload?.thread, - mentions: env?.payload?.mentions, - timestamp: new Date().toISOString(), - }); - - // Send to ALL open WebSocket connections (multi-tab support) - for (const ws of session.webSockets) { - if (ws.readyState === 1) { // OPEN - ws.send(message); - } - } - } - - /** - * Admin: Add a member to a channel (does not require member to be connected). - * Used to sync channel memberships from database. - * Uses the first available user session or creates a temporary one. - */ - async adminJoinChannel(channel: string, member: string): Promise { - // Try to use an existing session - const sessions = Array.from(this.users.values()); - if (sessions.length > 0) { - const session = sessions[0]; - if (session.relayClient.adminJoinChannel) { - console.log(`[user-bridge] Admin join: ${member} -> ${channel} (via ${session.username})`); - return session.relayClient.adminJoinChannel(channel, member); - } - } - - // No sessions available - create a temporary system client - try { - console.log(`[user-bridge] Admin join: ${member} -> ${channel} (creating temp client)`); - const tempClient = await this.createRelayClient({ - agentName: '__system__', - entityType: 'user', - }); - await tempClient.connect(); - - // Give daemon time to complete handshake before sending admin commands. - // 100ms is sufficient for local Unix socket handshake (typically <10ms), - // but provides margin for the daemon to process the HELLO message and - // set up internal state. This is a temporary client created just for - // the admin operation, not a long-lived session. - await new Promise(resolve => setTimeout(resolve, 100)); - - if (tempClient.adminJoinChannel) { - const result = tempClient.adminJoinChannel(channel, member); - // Disconnect after a short delay to allow message to be sent - setTimeout(() => tempClient.disconnect(), 200); - return result; - } - - tempClient.disconnect(); - return false; - } catch (err) { - console.error('[user-bridge] Failed to create temp client for admin join:', err); - return false; - } - } - - /** - * Admin: Remove a member from a channel (does not require member to be connected). - * Used to remove channel members from dashboard. - * Uses the first available user session or creates a temporary one. - */ - async adminRemoveMember(channel: string, member: string): Promise { - // Try to use an existing session - const sessions = Array.from(this.users.values()); - if (sessions.length > 0) { - const session = sessions[0]; - if (session.relayClient.adminRemoveMember) { - console.log(`[user-bridge] Admin remove: ${member} <- ${channel} (via ${session.username})`); - return session.relayClient.adminRemoveMember(channel, member); - } - } - - // No sessions available - create a temporary system client - try { - console.log(`[user-bridge] Admin remove: ${member} <- ${channel} (creating temp client)`); - const tempClient = await this.createRelayClient({ - agentName: '__system__', - entityType: 'user', - }); - await tempClient.connect(); - - // Give daemon time to complete handshake before sending admin commands. - // 100ms is sufficient for local Unix socket handshake (typically <10ms), - // but provides margin for the daemon to process the HELLO message and - // set up internal state. This is a temporary client created just for - // the admin operation, not a long-lived session. - await new Promise(resolve => setTimeout(resolve, 100)); - - if (tempClient.adminRemoveMember) { - const result = tempClient.adminRemoveMember(channel, member); - // Disconnect after a short delay to allow message to be sent - setTimeout(() => tempClient.disconnect(), 200); - return result; - } - - tempClient.disconnect(); - return false; - } catch (err) { - console.error('[user-bridge] Failed to create temp client for admin remove:', err); - return false; - } - } - - /** - * Dispose of all user sessions. - */ - dispose(): void { - for (const [username] of this.users) { - this.unregisterUser(username); - } - console.log('[user-bridge] Disposed all user sessions'); - } -} diff --git a/packages/dashboard-server/src/types/index.ts b/packages/dashboard-server/src/types/index.ts index 08185d3..c879334 100644 --- a/packages/dashboard-server/src/types/index.ts +++ b/packages/dashboard-server/src/types/index.ts @@ -7,66 +7,6 @@ import type { Express } from 'express'; import type { Server } from 'http'; import type { WebSocketServer } from 'ws'; -import type { RelayAdapter } from '@agent-relay/sdk'; - -/** - * Interface for spawn manager read operations. - * When the broker SpawnManager is passed in, the dashboard uses it - * for read operations (logs, worker listing). - * Spawn/release go through the SDK client → broker socket. - */ -export interface SpawnManagerLike { - hasWorker(name: string): boolean; - getWorkerOutput(name: string, limit?: number): string[] | undefined; - getWorkerRawOutput(name: string): string | undefined; - getActiveWorkers(): Array<{ - name: string; - cli: string; - task: string; - team?: string; - spawnerName?: string; - spawnedAt: number; - pid?: number; - }>; - sendWorkerInput(name: string, data: string): boolean | Promise; -} - -/** - * Options for starting the dashboard server - */ -export interface DashboardOptions { - /** Port to listen on */ - port: number; - /** Data directory for storage */ - dataDir: string; - /** Team directory for configuration */ - teamDir: string; - /** Path to SQLite database (defaults to dataDir/messages.sqlite - same as broker runtime) */ - dbPath?: string; - /** Enable agent spawning API */ - enableSpawner?: boolean; - /** Project root for spawner (defaults to dataDir) */ - projectRoot?: string; - /** Tmux session name for workers */ - tmuxSession?: string; - /** Enable verbose logging (WebSocket connections, broadcasts, etc.) */ - verbose?: boolean; - /** - * Callback to mark an agent as spawning (before HELLO completes). - * Messages sent to this agent can be queued for delivery after registration. - */ - onMarkSpawning?: (agentName: string) => void; - /** - * Callback to clear the spawning flag for an agent. - * Called when spawn fails or is cancelled. - */ - onClearSpawning?: (agentName: string) => void; - /** - * RelayAdapter instance for broker mode. - * Provides spawn/release/list/messaging via the broker binary. - */ - relayAdapter?: RelayAdapter; -} /** * Options for the proxy/mock server (simpler mode) @@ -139,20 +79,3 @@ export interface ThreadMetadata { participants?: string[]; } -/** - * Server context passed to route handlers - */ -export interface ServerContext { - /** Data directory */ - dataDir: string; - /** Team directory */ - teamDir: string; - /** Database path */ - dbPath?: string; - /** Project root */ - projectRoot?: string; - /** Default workspace ID */ - defaultWorkspaceId?: string; - /** Enable spawner */ - enableSpawner?: boolean; -} diff --git a/packages/dashboard-server/src/websocket/bridge.ts b/packages/dashboard-server/src/websocket/bridge.ts deleted file mode 100644 index 391ad5c..0000000 --- a/packages/dashboard-server/src/websocket/bridge.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { WebSocket, type WebSocketServer } from 'ws'; - -export interface BridgeWebSocketDeps { - wssBridge: WebSocketServer; - bridgeClientAlive: WeakMap; - getBridgeData: () => Promise; - debug: (message: string) => void; -} - -/** - * Bridge WebSocket handler for cross-project dashboard state. - */ -export function setupBridgeWebSocket(deps: BridgeWebSocketDeps): void { - const { wssBridge, bridgeClientAlive, getBridgeData, debug } = deps; - - wssBridge.on('connection', async (ws) => { - debug('[dashboard] Bridge WebSocket client connected'); - - // Mark client as alive initially for ping/pong keepalive. - bridgeClientAlive.set(ws, true); - - // Handle pong responses (keep connection alive). - ws.on('pong', () => { - bridgeClientAlive.set(ws, true); - }); - - try { - const data = await getBridgeData(); - const payload = JSON.stringify(data); - if (ws.readyState === WebSocket.OPEN) { - ws.send(payload); - } - } catch (err) { - console.error('[dashboard] Failed to send initial bridge data:', err); - } - - ws.on('error', (err) => { - console.error('[dashboard] Bridge WebSocket client error:', err); - }); - - ws.on('close', (code, reason) => { - debug(`[dashboard] Bridge WebSocket client disconnected, code: ${code}, reason: ${reason?.toString() || 'none'}`); - }); - }); -} diff --git a/packages/dashboard-server/src/websocket/logs.ts b/packages/dashboard-server/src/websocket/logs.ts index 4adbac1..462fca1 100644 --- a/packages/dashboard-server/src/websocket/logs.ts +++ b/packages/dashboard-server/src/websocket/logs.ts @@ -1,7 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { WebSocket, type WebSocketServer } from 'ws'; -import type { MessageBuffer } from '../messageBuffer.js'; +import { WebSocket } from 'ws'; import { normalizeName } from '../lib/utils.js'; import { getWorkerLogsDir, @@ -200,479 +199,3 @@ export function handleStandaloneLogWebSocket( }); } -interface WorkerMeta { - name: string; - cli: string; - task: string; - spawnedAt: number; - pid?: number; - logFile?: string; -} - -interface SpawnReaderLike { - hasWorker: (name: string) => boolean; - getWorkerOutput: (name: string, maxLines?: number) => string[] | undefined; - getWorkerRawOutput?: (name: string) => string | undefined; - sendWorkerInput: (name: string, input: string) => Promise; -} - -export interface LogsWebSocketDeps { - wssLogs: WebSocketServer; - teamDir: string; - debug: (message: string) => void; - logSubscriptions: Map>; - fileWatchers: Map; - fileLastSize: Map; - agentLogBuffers: Map; - getAgentLogBuffer: (agentName: string) => MessageBuffer; - spawnReader?: SpawnReaderLike; -} - -/** - * Logs WebSocket handler for live output streaming and replay. - */ -export function setupLogsWebSocket(deps: LogsWebSocketDeps): void { - const { - wssLogs, - teamDir, - debug, - logSubscriptions, - fileWatchers, - fileLastSize, - agentLogBuffers, - getAgentLogBuffer, - spawnReader, - } = deps; - - // Track alive status for ping/pong keepalive on log connections. - const logClientAlive = new WeakMap(); - - // Ping interval for log WebSocket connections (15 seconds). - // Reduced from 30s to detect disconnects faster and minimize message loss window. - const LOG_PING_INTERVAL_MS = 15000; - const logPingInterval = setInterval(() => { - wssLogs.clients.forEach((ws) => { - if (logClientAlive.get(ws) === false) { - // Client didn't respond to last ping - close gracefully. - debug('[dashboard] Logs WebSocket client unresponsive, closing gracefully'); - ws.close(1000, 'unresponsive'); - return; - } - // Mark as not alive until we get a pong. - logClientAlive.set(ws, false); - ws.ping(); - }); - }, LOG_PING_INTERVAL_MS); - - // Clean up ping interval on server close. - wssLogs.on('close', () => { - clearInterval(logPingInterval); - }); - - // Handle logs WebSocket connections for live log streaming. - wssLogs.on('connection', (ws, req) => { - debug('[dashboard] Logs WebSocket client connected'); - const clientSubscriptions = new Set(); - - // Mark client as alive initially. - logClientAlive.set(ws, true); - - // Handle pong responses (keep connection alive). - ws.on('pong', () => { - logClientAlive.set(ws, true); - }); - - // Send sync message with current server timestamp so client can track its position. - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'sync', serverTimestamp: Date.now(), sequenceId: 0 })); - } - - // Helper to check if agent is daemon-connected (from agents.json). - const isDaemonConnected = (agentName: string): boolean => { - const agentsPath = path.join(teamDir, 'agents.json'); - if (!fs.existsSync(agentsPath)) return false; - try { - const data = JSON.parse(fs.readFileSync(agentsPath, 'utf-8')) as { agents?: Array<{ name: string }> }; - return data.agents?.some((a) => a.name === agentName) ?? false; - } catch { - return false; - } - }; - - // Helper to get worker info from workers.json (for externally-spawned workers). - const getExternalWorkerInfo = (agentName: string): WorkerMeta | null => { - const workersPath = path.join(teamDir, 'workers.json'); - if (!fs.existsSync(workersPath)) return null; - try { - const data = JSON.parse(fs.readFileSync(workersPath, 'utf-8')) as { workers?: WorkerMeta[] }; - const worker = data.workers?.find((w) => w.name === agentName); - return worker ?? null; - } catch { - return null; - } - }; - - // Helper to read raw log tail for externally-spawned workers. - const readLogFileTail = (logFile: string, maxBytes = LOG_HISTORY_MAX_BYTES): string => { - return readLogTailContent(logFile, maxBytes); - }; - - const buildLogPayload = ( - agentName: string, - content: string, - messageType: 'output' | 'log' = 'output', - ): string => { - const base = { - type: messageType, - agent: agentName, - data: content, - content, - timestamp: new Date().toISOString(), - }; - const serializedBase = JSON.stringify(base); - const seq = getAgentLogBuffer(agentName).push(messageType, serializedBase); - return JSON.stringify({ ...base, seq }); - }; - - // Helper to start watching a log file for live updates. - const watchLogFile = (agentName: string, logFile: string) => { - if (fileWatchers.has(agentName)) return; // Already watching. - if (!fs.existsSync(logFile)) return; - - try { - // Track current file size for incremental reads. - const stats = fs.statSync(logFile); - fileLastSize.set(agentName, stats.size); - - const watcher = fs.watch(logFile, (eventType) => { - if (eventType !== 'change') return; - - const clients = logSubscriptions.get(agentName); - if (!clients || clients.size === 0) { - // No subscribers, stop watching. - watcher.close(); - fileWatchers.delete(agentName); - fileLastSize.delete(agentName); - return; - } - - try { - const newStats = fs.statSync(logFile); - const lastSize = fileLastSize.get(agentName) || 0; - - if (newStats.size > lastSize) { - // Read only the new content. - const fd = fs.openSync(logFile, 'r'); - const buffer = Buffer.alloc(newStats.size - lastSize); - fs.readSync(fd, buffer, 0, buffer.length, lastSize); - fs.closeSync(fd); - - const newContent = buffer.toString('utf-8'); - fileLastSize.set(agentName, newStats.size); - - if (!newContent) { - return; - } - - const payload = buildLogPayload(agentName, newContent, 'output'); - - for (const client of clients) { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - } - } - } catch (err) { - console.error(`[dashboard] Error reading log file updates for ${agentName}:`, err); - } - }); - - fileWatchers.set(agentName, watcher); - } catch (err) { - console.error(`[dashboard] Failed to watch log file for ${agentName}:`, err); - } - }; - - const cleanupAgentSubscription = (agentName: string): void => { - const remainingClients = logSubscriptions.get(agentName); - if (!remainingClients || remainingClients.size === 0) { - const watcher = fileWatchers.get(agentName); - if (watcher) { - watcher.close(); - fileWatchers.delete(agentName); - fileLastSize.delete(agentName); - } - } - }; - - // Helper to subscribe to an agent (async to handle spawn timing). - const subscribeToAgent = async (agentName: string) => { - let isSpawned = spawnReader?.hasWorker(agentName) ?? false; - const isDaemon = isDaemonConnected(agentName); - - // Check if agent exists (either spawned or daemon-connected). - if (!isSpawned && !isDaemon) { - ws.send(JSON.stringify({ - type: 'error', - agent: agentName, - error: `Agent ${agentName} not found`, - })); - // Close with custom code 4404 to signal "agent not found" - client should not reconnect. - ws.close(4404, 'Agent not found'); - return false; - } - - // If agent is daemon-connected but not yet in spawner's activeWorkers, poll for registration. - if (!isSpawned && isDaemon && spawnReader) { - const isSetupAgent = agentName.startsWith('__setup__'); - const maxWaitMs = isSetupAgent ? 90000 : 5000; // 90s for setup agents (CLI auth can be slow), 5s otherwise. - const pollIntervalMs = 100; - const startTime = Date.now(); - - while (Date.now() - startTime < maxWaitMs) { - await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)); - isSpawned = spawnReader.hasWorker(agentName); - if (isSpawned) { - console.log(`[dashboard] Agent ${agentName} appeared in spawner after ${Date.now() - startTime}ms`); - break; - } - // Check if WebSocket was closed during wait. - if (ws.readyState !== WebSocket.OPEN) { - return false; - } - } - } - - // Add to subscriptions. - clientSubscriptions.add(agentName); - if (!logSubscriptions.has(agentName)) { - logSubscriptions.set(agentName, new Set()); - } - logSubscriptions.get(agentName)!.add(ws); - - debug(`[dashboard] Client subscribed to logs for: ${agentName} (spawned: ${isSpawned}, daemon: ${isDaemon})`); - - if (isSpawned && spawnReader) { - const rawHistory = - spawnReader.getWorkerRawOutput?.(agentName) - ?? (spawnReader.getWorkerOutput(agentName, 5000) || []).join('\n'); - const historyTail = takeTail(rawHistory, LOG_HISTORY_MAX_BYTES); - ws.send(JSON.stringify({ - type: 'history', - agent: agentName, - lines: historyTail ? [historyTail] : [], - })); - } else { - // Check if this is an externally-spawned worker with a log file. - const externalWorker = getExternalWorkerInfo(agentName); - let logFile = externalWorker?.logFile; - - // If no explicit logFile in workers.json, try conventional path. - if (!logFile) { - const conventionalPath = path.join(teamDir, 'worker-logs', `${agentName}.log`); - if (fs.existsSync(conventionalPath)) { - logFile = conventionalPath; - } - } - - if (logFile && fs.existsSync(logFile)) { - // Read logs from the external worker's log file. - const historyContent = readLogFileTail(logFile, LOG_HISTORY_MAX_BYTES); - ws.send(JSON.stringify({ - type: 'history', - agent: agentName, - lines: historyContent ? [historyContent] : [], - })); - // Start watching the log file for live updates. - watchLogFile(agentName, logFile); - } else { - // For daemon-connected agents without log files, explain PTY output limitations. - ws.send(JSON.stringify({ - type: 'history', - agent: agentName, - lines: [`[${agentName} is a daemon-connected agent - PTY output not available. Showing relay messages only.]`], - })); - } - } - - ws.send(JSON.stringify({ - type: 'sync', - serverTimestamp: Date.now(), - sequenceId: getAgentLogBuffer(agentName).currentId(), - })); - - ws.send(JSON.stringify({ - type: 'subscribed', - agent: agentName, - })); - - return true; - }; - - // Check if agent name is in URL path: /ws/logs/:agentName. - const pathname = new URL(req.url || '', `http://${req.headers.host}`).pathname; - const pathMatch = pathname.match(/^\/ws\/logs\/(.+)$/); - if (pathMatch) { - let agentName: string; - try { - agentName = decodeURIComponent(pathMatch[1]); - } catch { - ws.send(JSON.stringify({ - type: 'error', - error: 'Invalid log stream path encoding', - })); - ws.close(1003, 'invalid-path'); - return; - } - subscribeToAgent(agentName).catch((err) => { - console.error(`[dashboard] Error subscribing to ${agentName}:`, err); - }); - } - - ws.on('message', async (data) => { - try { - const msg = JSON.parse(data.toString()); - - // Subscribe to agent logs. - if (msg.subscribe && typeof msg.subscribe === 'string') { - subscribeToAgent(msg.subscribe).catch((err) => { - console.error(`[dashboard] Error subscribing to ${msg.subscribe}:`, err); - }); - } - - // Unsubscribe from agent logs. - if (msg.unsubscribe && typeof msg.unsubscribe === 'string') { - const agentName = msg.unsubscribe; - clientSubscriptions.delete(agentName); - logSubscriptions.get(agentName)?.delete(ws); - cleanupAgentSubscription(agentName); - - debug(`[dashboard] Client unsubscribed from logs for: ${agentName}`); - - ws.send(JSON.stringify({ - type: 'unsubscribed', - agent: agentName, - })); - } - - // Handle interactive terminal input. - if (msg.type === 'input' && typeof msg.data === 'string') { - // Get agent name from message or use first subscribed agent. - const agentName = msg.agent || [...clientSubscriptions][0]; - - if (!agentName) { - ws.send(JSON.stringify({ - type: 'error', - error: 'No agent subscribed for input', - })); - return; - } - - // Check if this is a spawned agent (we can only send input to spawned agents). - if (spawnReader?.hasWorker(agentName)) { - const success = await spawnReader.sendWorkerInput(agentName, msg.data); - if (!success) { - console.warn(`[dashboard] Failed to send input to agent ${agentName}`); - } - } else { - // Daemon-connected agents don't support direct input. - ws.send(JSON.stringify({ - type: 'error', - agent: agentName, - error: 'Interactive input not supported for daemon-connected agents', - })); - } - } - - // Handle replay request: - // { type: "replay", agent: "name", lastSequenceId: N } (preferred) - // { type: "replay", agent: "name", lastTimestamp: N } (legacy fallback) - if (msg.type === 'replay' && typeof msg.agent === 'string') { - const logBuffer = agentLogBuffers.get(msg.agent); - if (!logBuffer || ws.readyState !== WebSocket.OPEN) { - return; - } - - try { - const missed = typeof msg.lastSequenceId === 'number' - ? logBuffer.getAfter(msg.lastSequenceId) - : typeof msg.lastTimestamp === 'number' - ? logBuffer.getAfterTimestamp(msg.lastTimestamp) - : []; - - const messages = missed.map((m) => { - try { - const parsed = JSON.parse(m.payload); - if (parsed && typeof parsed === 'object') { - return { ...(parsed as Record), seq: m.id }; - } - } catch { - // Fallback to raw payload. - } - - return { - type: 'output', - agent: msg.agent, - data: m.payload, - content: m.payload, - timestamp: new Date(m.timestamp).toISOString(), - seq: m.id, - }; - }); - - const entries = messages.map((message) => { - const typed = message as { content?: unknown; data?: unknown; timestamp?: unknown }; - const content = typeof typed.content === 'string' - ? typed.content - : typeof typed.data === 'string' - ? typed.data - : ''; - const timestamp = typeof typed.timestamp === 'string' - ? Date.parse(typed.timestamp) - : NaN; - return { - content, - timestamp: Number.isFinite(timestamp) ? timestamp : Date.now(), - }; - }); - - ws.send(JSON.stringify({ - type: 'replay', - agent: msg.agent, - messages, - entries, - })); - } catch (err) { - console.error('[dashboard] Failed to replay log messages:', err); - } - } - } catch (err) { - console.error('[dashboard] Invalid logs WebSocket message:', err); - } - }); - - ws.on('error', (err) => { - console.error('[dashboard] Logs WebSocket client error:', err); - }); - - ws.on('close', (code, reason) => { - // Clean up subscriptions on disconnect. - for (const agentName of clientSubscriptions) { - logSubscriptions.get(agentName)?.delete(ws); - const remainingClients = logSubscriptions.get(agentName); - if (!remainingClients || remainingClients.size === 0) { - const watcher = fileWatchers.get(agentName); - if (watcher) { - watcher.close(); - fileWatchers.delete(agentName); - fileLastSize.delete(agentName); - agentLogBuffers.delete(agentName); - console.log(`[dashboard] Stopped watching log file for: ${agentName}`); - } - } - } - const reasonStr = reason?.toString() || 'no reason'; - console.log(`[dashboard] Logs WebSocket client disconnected (code: ${code}, reason: ${reasonStr})`); - }); - }); -} diff --git a/packages/dashboard-server/src/websocket/main.ts b/packages/dashboard-server/src/websocket/main.ts deleted file mode 100644 index b487e08..0000000 --- a/packages/dashboard-server/src/websocket/main.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { WebSocket, type WebSocketServer } from 'ws'; -import type { MessageBuffer } from '../messageBuffer.js'; - -export interface MainWebSocketDeps { - wss: WebSocketServer; - mainClientAlive: WeakMap; - mainMessageBuffer: MessageBuffer; - initializingClients: WeakSet; - getAllData: () => Promise; - debug: (message: string) => void; -} - -/** - * Main dashboard WebSocket handler: initial snapshot + replay support. - */ -export function setupMainWebSocket(deps: MainWebSocketDeps): void { - const { - wss, - mainClientAlive, - mainMessageBuffer, - initializingClients, - getAllData, - debug, - } = deps; - - wss.on('connection', async (ws, req) => { - debug(`[dashboard] WebSocket client connected from: ${req.socket.remoteAddress}`); - - // Mark client as alive initially for ping/pong keepalive. - mainClientAlive.set(ws, true); - - // Handle pong responses (keep connection alive). - ws.on('pong', () => { - mainClientAlive.set(ws, true); - }); - - // Send current sequence ID so client can track its position. - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'sync', sequenceId: mainMessageBuffer.currentId() })); - } - - // Mark as initializing to prevent broadcaster races. - initializingClients.add(ws); - - try { - const data = await getAllData(); - - if (!data) { - // Team data temporarily unavailable — defer initial send. - debug('[dashboard] Team data unavailable on connect - deferring initial send to next broadcast'); - } else if (ws.readyState === WebSocket.OPEN) { - const payload = JSON.stringify(data); - debug(`[dashboard] Sending initial data, size: ${payload.length}, first 200 chars: ${payload.substring(0, 200)}`); - ws.send(payload); - debug('[dashboard] Initial data sent successfully'); - } else { - console.warn('[dashboard] WebSocket not open, state:', ws.readyState); - } - } catch (err) { - console.error('[dashboard] Failed to send initial data:', err); - } finally { - // Now allow broadcastData to send to this client. - initializingClients.delete(ws); - } - - // Handle replay requests. - ws.on('message', (data) => { - try { - const msg = JSON.parse(data.toString()); - - // Handle replay request: { type: "replay", lastSequenceId: N }. - if (msg.type === 'replay' && typeof msg.lastSequenceId === 'number') { - const missed = mainMessageBuffer.getAfter(msg.lastSequenceId); - const gapMs = missed.length > 0 ? Date.now() - missed[0].timestamp : 0; - - console.log(`[dashboard] Client replaying ${missed.length} missed messages (gap: ${gapMs}ms)`); - - // Send each missed message with its original sequence ID. - for (const buffered of missed) { - if (ws.readyState === WebSocket.OPEN) { - try { - const original = JSON.parse(buffered.payload); - ws.send(JSON.stringify({ seq: buffered.id, ...original })); - } catch (err) { - console.error('[dashboard] Failed to replay message:', err); - } - } - } - - // Send current sync position after replay. - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'sync', sequenceId: mainMessageBuffer.currentId() })); - } - } - } catch (err) { - // Non-JSON messages are ignored (binary, etc.). - debug(`[dashboard] Unhandled main WebSocket message: ${err}`); - } - }); - - ws.on('error', (err) => { - console.error('[dashboard] WebSocket client error:', err); - }); - - ws.on('close', (code, reason) => { - debug(`[dashboard] WebSocket client disconnected, code: ${code}, reason: ${reason?.toString() || 'none'}`); - }); - }); -} diff --git a/packages/dashboard-server/src/websocket/presence.ts b/packages/dashboard-server/src/websocket/presence.ts deleted file mode 100644 index 961d8be..0000000 --- a/packages/dashboard-server/src/websocket/presence.ts +++ /dev/null @@ -1,428 +0,0 @@ -import { WebSocket, type WebSocketServer } from 'ws'; -import type { MessageBuffer } from '../messageBuffer.js'; - -export interface UserPresenceInfo { - username: string; - avatarUrl?: string; - connectedAt: string; - lastSeen: string; -} - -export interface UserPresenceState { - info: UserPresenceInfo; - connections: Set; -} - -export interface ChannelMessageEvent { - type: 'channel_message'; - targetUser: string; - channel: string; - from: string; - fromAvatarUrl?: string; - fromEntityType?: 'user' | 'agent'; - body: string; - thread?: string; - mentions?: string[]; - timestamp: string; -} - -export interface DirectMessageEvent { - type: 'direct_message'; - targetUser: string; - from: string; - fromAvatarUrl?: string; - fromEntityType?: 'user' | 'agent'; - body: string; - id: string; - messageId: string; - timestamp: string; -} - -interface UserBridgeLike { - updateWebSocket: (username: string, ws: WebSocket) => void; - registerUser: (username: string, ws: WebSocket, metadata?: { avatarUrl?: string }) => Promise; - unregisterUser: (username: string) => void; - joinChannel: (username: string, channel: string) => Promise; - leaveChannel: (username: string, channel: string) => Promise; - sendChannelMessage: (username: string, channel: string, body: string, options?: { thread?: string }) => Promise; - sendDirectMessage: (from: string, to: string, body: string, options?: { thread?: string }) => Promise; -} - -export interface PresenceWebSocketDeps { - wss: WebSocketServer; - wssPresence: WebSocketServer; - mainMessageBuffer: MessageBuffer; - onlineUsers: Map; - presenceHealth: WeakMap; - broadcastPresence: (message: object, exclude?: WebSocket) => void; - isValidUsername: (username: unknown) => username is string; - isValidAvatarUrl: (avatarUrl: unknown) => avatarUrl is string | undefined; - getUserBridge: () => UserBridgeLike | undefined; - debug: (message: string) => void; -} - -export interface PresenceSetupResult { - broadcastChannelMessage: (message: ChannelMessageEvent) => void; - broadcastDirectMessage: (message: DirectMessageEvent) => void; -} - -/** - * Presence WebSocket handler and channel/direct message broadcasters. - */ -export function setupPresenceWebSocket(deps: PresenceWebSocketDeps): PresenceSetupResult { - const { - wss, - wssPresence, - mainMessageBuffer, - onlineUsers, - presenceHealth, - broadcastPresence, - isValidUsername, - isValidAvatarUrl, - getUserBridge, - debug, - } = deps; - - // Helper to broadcast channel messages to all connected clients. - // Broadcasts to both main wss (local mode) and wssPresence (cloud mode). - const broadcastChannelMessage = (message: ChannelMessageEvent) => { - // Push into buffer and wrap with sequence ID for replay support. - const rawPayload = JSON.stringify(message); - const seq = mainMessageBuffer.push('channel_message', rawPayload); - const payload = JSON.stringify({ seq, ...message }); - // Broadcast to main WebSocket clients (local mode). - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - }); - // Also broadcast to presence WebSocket clients (cloud mode). - wssPresence.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - }); - }; - - // Helper to broadcast direct messages to all connected clients. - // This enables agent replies to appear in the dashboard UI. - const broadcastDirectMessage = (message: DirectMessageEvent) => { - // Push into buffer and wrap with sequence ID for replay support. - const rawPayload = JSON.stringify(message); - const seq = mainMessageBuffer.push('direct_message', rawPayload); - const payload = JSON.stringify({ seq, ...message }); - - // Broadcast to main WebSocket clients (local mode). - const mainClients = Array.from(wss.clients).filter((c) => c.readyState === WebSocket.OPEN); - debug(`[dashboard] Broadcasting direct_message to ${mainClients.length} main clients`); - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - }); - - const presenceClients = Array.from(wssPresence.clients).filter((c) => c.readyState === WebSocket.OPEN); - if (presenceClients.length > 0) { - debug(`[dashboard] Broadcasting direct_message to ${presenceClients.length} presence clients`); - wssPresence.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(payload); - } - }); - } - }; - - // Helper to get online users list (without ws references). - const getOnlineUsersList = (): UserPresenceInfo[] => { - return Array.from(onlineUsers.values()).map((state) => state.info); - }; - - // Heartbeat to detect dead connections (30 seconds). - const PRESENCE_HEARTBEAT_INTERVAL = 30000; - const presenceHeartbeat = setInterval(() => { - wssPresence.clients.forEach((ws) => { - const health = presenceHealth.get(ws); - if (!health) { - presenceHealth.set(ws, { isAlive: true }); - return; - } - if (!health.isAlive) { - ws.terminate(); - return; - } - health.isAlive = false; - ws.ping(); - }); - }, PRESENCE_HEARTBEAT_INTERVAL); - - wssPresence.on('close', () => { - clearInterval(presenceHeartbeat); - }); - - wssPresence.on('connection', (ws) => { - // Initialize health tracking (no log - too noisy). - presenceHealth.set(ws, { isAlive: true }); - - ws.on('pong', () => { - const health = presenceHealth.get(ws); - if (health) health.isAlive = true; - }); - - let clientUsername: string | undefined; - - ws.on('message', (data) => { - try { - const msg = JSON.parse(data.toString()) as Record; - - if (msg.type === 'presence') { - if (msg.action === 'join' && typeof msg.user === 'object' && msg.user !== null && 'username' in msg.user) { - const user = msg.user as { username?: unknown; avatarUrl?: unknown }; - const username = user.username; - const avatarUrl = user.avatarUrl; - - // Validate inputs. - if (!isValidUsername(username)) { - console.warn(`[dashboard] Invalid username rejected: ${username}`); - return; - } - if (!isValidAvatarUrl(avatarUrl)) { - console.warn(`[dashboard] Invalid avatar URL rejected for user ${username}`); - return; - } - - clientUsername = username; - const now = new Date().toISOString(); - - // Check if user already has connections (multi-tab support). - const existing = onlineUsers.get(username); - if (existing) { - // Add this connection to existing user. - existing.connections.add(ws); - existing.info.lastSeen = now; - - // Update userBridge to use the new WebSocket for message delivery. - getUserBridge()?.updateWebSocket(username, ws); - - // Only log at milestones to reduce noise. - const count = existing.connections.size; - if (count === 2 || count === 5 || count === 10 || count % 50 === 0) { - console.log(`[dashboard] User ${username} has ${count} connections`); - } - } else { - // New user - create presence state. - onlineUsers.set(username, { - info: { - username, - avatarUrl, - connectedAt: now, - lastSeen: now, - }, - connections: new Set([ws]), - }); - - console.log(`[dashboard] User ${username} came online`); - - // Register user for messaging. - getUserBridge()?.registerUser(username, ws, { avatarUrl }).catch((err: unknown) => { - console.error(`[dashboard] Failed to register user ${username} with relay:`, err); - }); - - // Broadcast join to all other clients (only for truly new users). - broadcastPresence({ - type: 'presence_join', - user: { - username, - avatarUrl, - connectedAt: now, - lastSeen: now, - }, - }, ws); - } - - // Send current online users list to the new client. - ws.send(JSON.stringify({ - type: 'presence_list', - users: getOnlineUsersList(), - })); - } else if (msg.action === 'leave') { - // Security: Only allow leaving your own username. Must be authenticated first. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated leave attempt'); - return; - } - if (msg.username !== clientUsername) { - console.warn(`[dashboard] Security: User ${clientUsername} tried to remove ${String(msg.username)}`); - return; - } - - // Remove this connection from the user's set. - const username = clientUsername; - const userState = onlineUsers.get(username); - if (userState) { - userState.connections.delete(ws); - - // Only broadcast leave if no more connections. - if (userState.connections.size === 0) { - onlineUsers.delete(username); - console.log(`[dashboard] User ${username} went offline`); - - broadcastPresence({ - type: 'presence_leave', - username, - }); - } else { - console.log(`[dashboard] User ${username} closed tab (${userState.connections.size} remaining)`); - } - } - } - } else if (msg.type === 'typing') { - // Must have authenticated first. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated typing attempt'); - return; - } - // Validate typing message comes from authenticated user. - if (msg.username !== clientUsername) { - console.warn('[dashboard] Security: Typing message username mismatch'); - return; - } - - // Update last seen. - const username = clientUsername; - const userState = onlineUsers.get(username); - if (userState) { - userState.info.lastSeen = new Date().toISOString(); - } - - // Broadcast typing indicator to all other clients. - broadcastPresence({ - type: 'typing', - username, - avatarUrl: userState?.info.avatarUrl, - isTyping: msg.isTyping, - }, ws); - } else if (msg.type === 'channel_join') { - // Join a channel. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated channel_join attempt'); - return; - } - if (!msg.channel || typeof msg.channel !== 'string') { - console.warn('[dashboard] Invalid channel_join: missing channel'); - return; - } - getUserBridge()?.joinChannel(clientUsername, msg.channel).then((success: boolean) => { - ws.send(JSON.stringify({ - type: 'channel_joined', - channel: msg.channel, - success, - })); - }).catch((err: unknown) => { - const message = err instanceof Error ? err.message : String(err); - console.error('[dashboard] Channel join error:', err); - ws.send(JSON.stringify({ - type: 'channel_joined', - channel: msg.channel, - success: false, - error: message, - })); - }); - } else if (msg.type === 'channel_leave') { - // Leave a channel. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated channel_leave attempt'); - return; - } - if (!msg.channel || typeof msg.channel !== 'string') { - console.warn('[dashboard] Invalid channel_leave: missing channel'); - return; - } - getUserBridge()?.leaveChannel(clientUsername, msg.channel).then((success: boolean) => { - ws.send(JSON.stringify({ - type: 'channel_left', - channel: msg.channel, - success, - })); - }).catch((err: unknown) => { - console.error('[dashboard] Channel leave error:', err); - }); - } else if (msg.type === 'channel_message') { - // Send message to channel. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated channel_message attempt'); - return; - } - if (!msg.channel || typeof msg.channel !== 'string') { - console.warn('[dashboard] Invalid channel_message: missing channel'); - return; - } - if (!msg.body || typeof msg.body !== 'string') { - console.warn('[dashboard] Invalid channel_message: missing body'); - return; - } - getUserBridge()?.sendChannelMessage(clientUsername, msg.channel, msg.body, { - thread: typeof msg.thread === 'string' ? msg.thread : undefined, - }).catch((err: unknown) => { - console.error('[dashboard] Channel message error:', err); - }); - } else if (msg.type === 'direct_message') { - // Send direct message to user or agent. - if (!clientUsername) { - console.warn('[dashboard] Security: Unauthenticated direct_message attempt'); - return; - } - if (!msg.to || typeof msg.to !== 'string') { - console.warn("[dashboard] Invalid direct_message: missing 'to'"); - return; - } - if (!msg.body || typeof msg.body !== 'string') { - console.warn('[dashboard] Invalid direct_message: missing body'); - return; - } - getUserBridge()?.sendDirectMessage(clientUsername, msg.to, msg.body, { - thread: typeof msg.thread === 'string' ? msg.thread : undefined, - }).catch((err: unknown) => { - console.error('[dashboard] Direct message error:', err); - }); - } - } catch (err) { - console.error('[dashboard] Invalid presence message:', err); - } - }); - - ws.on('error', (err) => { - console.error('[dashboard] Presence WebSocket client error:', err); - }); - - ws.on('close', () => { - // Clean up on disconnect with multi-tab support. - if (clientUsername) { - const userState = onlineUsers.get(clientUsername); - if (userState) { - userState.connections.delete(ws); - - // Only broadcast leave if no more connections. - if (userState.connections.size === 0) { - onlineUsers.delete(clientUsername); - console.log(`[dashboard] User ${clientUsername} disconnected`); - - // Unregister from messaging bridge. - getUserBridge()?.unregisterUser(clientUsername); - - broadcastPresence({ - type: 'presence_leave', - username: clientUsername, - }); - } else { - console.log(`[dashboard] User ${clientUsername} closed connection (${userState.connections.size} remaining)`); - } - } - } - }); - }); - - return { - broadcastChannelMessage, - broadcastDirectMessage, - }; -}