diff --git a/policy-site/.gitignore b/policy-site/.gitignore
new file mode 100644
index 00000000..e314bcb5
--- /dev/null
+++ b/policy-site/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+dist/
+# Built by scripts/build-policy-wasm.sh from crates/didbot-policy-check.
+public/wasm/
diff --git a/policy-site/.npmrc b/policy-site/.npmrc
new file mode 100644
index 00000000..dc043ffe
--- /dev/null
+++ b/policy-site/.npmrc
@@ -0,0 +1,2 @@
+# No dependency runs code at install time.
+ignore-scripts=true
diff --git a/policy-site/index.html b/policy-site/index.html
new file mode 100644
index 00000000..1fd23751
--- /dev/null
+++ b/policy-site/index.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Policies
+
+
+
+
+
+
+
+ Bindings
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/policy-site/package-lock.json b/policy-site/package-lock.json
new file mode 100644
index 00000000..091ca77d
--- /dev/null
+++ b/policy-site/package-lock.json
@@ -0,0 +1,1865 @@
+{
+ "name": "policy-site",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "policy-site",
+ "version": "0.0.1",
+ "dependencies": {
+ "@atproto/common-web": "0.5.12",
+ "@atproto/oauth-client-browser": "0.5.7",
+ "@atproto/syntax": "0.7.6",
+ "@github/combobox-nav": "3.0.2"
+ },
+ "devDependencies": {
+ "@atproto/oauth-types": "0.7.6",
+ "linkedom": "0.18.13",
+ "typescript": "7.0.2",
+ "vite": "8.3.0"
+ },
+ "engines": {
+ "node": ">=22.18.0"
+ }
+ },
+ "node_modules/@atproto-labs/did-resolver": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.3.9.tgz",
+ "integrity": "sha512-KBgUdQQ76XL/xjQbuMfVBOEa5jaragR9NPq1uMoM4iXKnW5GGcEMHMPu+WrTxYNBku0b7MhPUfCs1pKtyLBP6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/fetch": "^0.3.6",
+ "@atproto-labs/pipe": "^0.2.4",
+ "@atproto-labs/simple-store": "^0.5.1",
+ "@atproto-labs/simple-store-memory": "^0.2.6",
+ "@atproto/did": "^0.5.5",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/fetch": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.3.6.tgz",
+ "integrity": "sha512-ro/J/cnqIp4bnSzDl8N3SkruvQcE3Pze+1H70mKdFfyFcdhPr+7O4lggl4VCbhI84Yqgz47c0xC6F/Yl39SxDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/pipe": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/handle-resolver": {
+ "version": "0.4.9",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.4.9.tgz",
+ "integrity": "sha512-I2xrVsgw0M1Gp5qAWQwX8aoq6E79PnNHuWX+mg7zMRpuazA5DWiHoJOyBZHPeEtbtYO3F8g3le7PRL8imaWljw==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/simple-store": "^0.5.1",
+ "@atproto-labs/simple-store-memory": "^0.2.6",
+ "@atproto/did": "^0.5.5",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/identity-resolver": {
+ "version": "0.4.9",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.4.9.tgz",
+ "integrity": "sha512-dVfbDHybOlDimEdUSbDfCQpMbmf25LNpffxCqSE//hLD86wiUzJQwJn2sHfskZTfwGD896wyY8musfrR3vDnOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/did-resolver": "^0.3.9",
+ "@atproto-labs/handle-resolver": "^0.4.9"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/pipe": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.2.4.tgz",
+ "integrity": "sha512-n67jCcrC+ouAeO10cWkpPzzLMlDi/lDCU30Us+LGqhOPhT6c4t5ASdBLQi9W3jUQtRzQBt3G9zipF+xKWNvVbw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/simple-store": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.5.1.tgz",
+ "integrity": "sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto-labs/simple-store-memory": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.2.6.tgz",
+ "integrity": "sha512-DD1v7MEfYF3BAcEpMTTpzfBLWLoI2HuyBhku2YpjHoqPrRrhCtE1alkxfPHjtjJVIjuU/XNU0cF/wB3+5FiKsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/simple-store": "^0.5.1",
+ "lru-cache": "^10.2.0"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/common-web": {
+ "version": "0.5.12",
+ "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.5.12.tgz",
+ "integrity": "sha512-FumNnUOUsk68gP+jU0sXY2wY2piqFV9/qU2YaoQq/IZknhJBlLYCzjQXXCN0FCzxAZWD0YUzgydeyuRvFoAxfA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lex-data": "^0.1.7",
+ "@atproto/lex-json": "^0.1.6",
+ "@atproto/syntax": "^0.7.6",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/did": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.5.5.tgz",
+ "integrity": "sha512-ibCxTPsPQtrtX+iidnE0bkMBHuoWFBdVfPFfCAlUKY6HfkqdUUxItzvQu9AMucYDhP6vJ15HLVj+QhNY2jtIvw==",
+ "license": "MIT",
+ "dependencies": {
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/jwk": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.7.4.tgz",
+ "integrity": "sha512-tq7TUDmNfe1yDfpRgdGQMJdl9TUlJmREQNCag9yg5w8Evu+TOiFiLgiOCbo7X4ouRPSgd1DpOzXbUa8UyKKMZA==",
+ "license": "MIT",
+ "dependencies": {
+ "multiformats": "^13.0.0",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/jwk-jose": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.2.4.tgz",
+ "integrity": "sha512-gzDoA0JTwnc0ZJOBLM7WX9xFxtynRS2K1Bofb8epzoMWDQvyvfbPcfkdPrKFM7NXCFUVpGpBnsCB8KFPTf1rCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/jwk": "^0.7.4",
+ "jose": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/jwk-webcrypto": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.3.4.tgz",
+ "integrity": "sha512-UsFIUozqnRecXPo6HgKV4PW4FqYHxX1V3iAe0rRV6Q2RSfYD8V2mZ89pv8NpvJynnaqJArBQ7HZlcg0F4tRYhA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/jwk": "^0.7.4",
+ "@atproto/jwk-jose": "^0.2.4",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/lex-data": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.1.7.tgz",
+ "integrity": "sha512-kW/dPLqo/WgCLV+XESR4JKwV6c1rZWJGOfuPupZGTjEDAKoBbKXdaEzX9/1vKQYbZ9U3j0DS/n7OFFK7wBugyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "multiformats": "^13.0.0",
+ "tslib": "^2.8.1",
+ "unicode-segmenter": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/lex-json": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.1.6.tgz",
+ "integrity": "sha512-mvrAd0lbyuecIHjyld8QN6MN6CBf4j0GCxLzegsvLh0SvDf+GbYWklkcQqmITL44yFQOwmA/QNIQj0Uvh7+R/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lex-data": "^0.1.7",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/lexicon": {
+ "version": "0.7.14",
+ "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.7.14.tgz",
+ "integrity": "sha512-hDG2+61JTrvxDFtoojDFxKmZkFVl/lR/jFi82gSN0zNDjr+dlpUGKlG9MoPBkUKK83+Pi+I2jZ55xXboJq6JTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/common-web": "^0.5.12",
+ "@atproto/syntax": "^0.7.6",
+ "multiformats": "^13.0.0",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/oauth-client": {
+ "version": "0.8.7",
+ "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.8.7.tgz",
+ "integrity": "sha512-DbWMFcy6t2HyyPKVEhKeh8xNNwM6sokPl/Qk7ctZLvEu+FT4GKsUppgf2hkWqeUyVc7nacu+nE7dCkYwPZmBKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/did-resolver": "^0.3.9",
+ "@atproto-labs/fetch": "^0.3.6",
+ "@atproto-labs/handle-resolver": "^0.4.9",
+ "@atproto-labs/identity-resolver": "^0.4.9",
+ "@atproto-labs/simple-store": "^0.5.1",
+ "@atproto-labs/simple-store-memory": "^0.2.6",
+ "@atproto/did": "^0.5.5",
+ "@atproto/jwk": "^0.7.4",
+ "@atproto/oauth-types": "^0.7.6",
+ "@atproto/xrpc": "^0.8.13",
+ "core-js": "^3.50.0",
+ "multiformats": "^13.0.0",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/oauth-client-browser": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@atproto/oauth-client-browser/-/oauth-client-browser-0.5.7.tgz",
+ "integrity": "sha512-AvCl5Of2NDW/yUhDSyOufmhmGnfa9ScNPSzOVYLDrJvm2t/5DdHoRu4gmtwwe2L6rf6hbUUFziA80drJm+0D0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto-labs/did-resolver": "^0.3.9",
+ "@atproto-labs/handle-resolver": "^0.4.9",
+ "@atproto-labs/simple-store": "^0.5.1",
+ "@atproto/did": "^0.5.5",
+ "@atproto/jwk": "^0.7.4",
+ "@atproto/jwk-webcrypto": "^0.3.4",
+ "@atproto/oauth-client": "^0.8.7",
+ "@atproto/oauth-types": "^0.7.6",
+ "core-js": "^3.50.0"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/oauth-types": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.7.6.tgz",
+ "integrity": "sha512-FgFe2mJOUgJSQBxKBwLWv+lFyDxBnlqA+wfSLabCNnabtNuSO3UQI0CGI72K7/OQRQvdwkcYQMoUuw3Rqex/mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/did": "^0.5.5",
+ "@atproto/jwk": "^0.7.4",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/syntax": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.7.6.tgz",
+ "integrity": "sha512-luKQTcWw1H1jLmYvX34ldBqNjz2orF082njmcF9fxbWTqVhsO+TpY0FHRKVPoV7dwL0Y3L16DNz1ma0LFGH25g==",
+ "license": "MIT",
+ "dependencies": {
+ "iso-datestring-validator": "^2.2.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@atproto/xrpc": {
+ "version": "0.8.13",
+ "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.8.13.tgz",
+ "integrity": "sha512-htpi93/Zqyk5WsT+RrWemxMcJFJx2M4VT8i8avuWJTyr7AZ42qvPsIdVsaPdOOS3rZwodq/SdZXJFgspJ4yc5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lexicon": "^0.7.14",
+ "zod": "^3.23.8"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@github/combobox-nav": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-3.0.2.tgz",
+ "integrity": "sha512-txEw3G7oK2b7G5olc5+xDEUJcHvyp9N+yx+LIjAwKsfOq22CGAx5zUL/6yVsHiCF/tRZlKMuFTAgpOHoCrNrzA==",
+ "license": "MIT"
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.149.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz",
+ "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz",
+ "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz",
+ "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz",
+ "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz",
+ "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz",
+ "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz",
+ "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz",
+ "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz",
+ "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz",
+ "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz",
+ "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz",
+ "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz",
+ "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz",
+ "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz",
+ "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz",
+ "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript/typescript-aix-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
+ "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
+ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
+ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
+ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
+ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
+ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-loong64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
+ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-mips64el": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
+ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
+ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-riscv64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
+ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-s390x": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
+ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
+ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-sunos-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
+ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
+ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
+ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz",
+ "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "3.50.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz",
+ "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/css-select": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz",
+ "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^2.0.0",
+ "css-what": "^8.0.0",
+ "domhandler": "^6.0.1",
+ "domutils": "^4.0.2",
+ "nth-check": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-8.0.0.tgz",
+ "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz",
+ "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^3.0.0",
+ "domhandler": "^6.0.0",
+ "entities": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz",
+ "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/domhandler": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz",
+ "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz",
+ "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^3.0.0",
+ "domelementtype": "^3.0.0",
+ "domhandler": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/entities": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
+ "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "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": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "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": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+ "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/htmlparser2": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.2.2",
+ "entities": "^7.0.1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/htmlparser2/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/iso-datestring-validator": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
+ "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
+ "license": "MIT"
+ },
+ "node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/linkedom": {
+ "version": "0.18.13",
+ "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.13.tgz",
+ "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "css-select": "^7.0.0",
+ "cssom": "^0.5.0",
+ "html-escaper": "^3.0.3",
+ "htmlparser2": "^10.1.0",
+ "uhyphen": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "canvas": ">= 2"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/multiformats": {
+ "version": "13.4.2",
+ "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz",
+ "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==",
+ "license": "Apache-2.0 OR MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-3.0.1.tgz",
+ "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.18",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
+ "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.149.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.8",
+ "@rolldown/binding-android-arm64": "1.2.8",
+ "@rolldown/binding-darwin-arm64": "1.2.8",
+ "@rolldown/binding-darwin-x64": "1.2.8",
+ "@rolldown/binding-freebsd-x64": "1.2.8",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.8",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.8",
+ "@rolldown/binding-linux-arm64-musl": "1.2.8",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.8",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.8",
+ "@rolldown/binding-linux-x64-gnu": "1.2.8",
+ "@rolldown/binding-linux-x64-musl": "1.2.8",
+ "@rolldown/binding-openharmony-arm64": "1.2.8",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.8",
+ "@rolldown/binding-win32-x64-msvc": "1.2.8"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "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/typescript": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
+ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc"
+ },
+ "engines": {
+ "node": ">=16.20.0"
+ },
+ "optionalDependencies": {
+ "@typescript/typescript-aix-ppc64": "7.0.2",
+ "@typescript/typescript-darwin-arm64": "7.0.2",
+ "@typescript/typescript-darwin-x64": "7.0.2",
+ "@typescript/typescript-freebsd-arm64": "7.0.2",
+ "@typescript/typescript-freebsd-x64": "7.0.2",
+ "@typescript/typescript-linux-arm": "7.0.2",
+ "@typescript/typescript-linux-arm64": "7.0.2",
+ "@typescript/typescript-linux-loong64": "7.0.2",
+ "@typescript/typescript-linux-mips64el": "7.0.2",
+ "@typescript/typescript-linux-ppc64": "7.0.2",
+ "@typescript/typescript-linux-riscv64": "7.0.2",
+ "@typescript/typescript-linux-s390x": "7.0.2",
+ "@typescript/typescript-linux-x64": "7.0.2",
+ "@typescript/typescript-netbsd-arm64": "7.0.2",
+ "@typescript/typescript-netbsd-x64": "7.0.2",
+ "@typescript/typescript-openbsd-arm64": "7.0.2",
+ "@typescript/typescript-openbsd-x64": "7.0.2",
+ "@typescript/typescript-sunos-x64": "7.0.2",
+ "@typescript/typescript-win32-arm64": "7.0.2",
+ "@typescript/typescript-win32-x64": "7.0.2"
+ }
+ },
+ "node_modules/uhyphen": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
+ "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/unicode-segmenter": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz",
+ "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==",
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
+ "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.28",
+ "rolldown": "~1.2.6",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.7.1",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "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"
+ }
+ }
+ }
+}
diff --git a/policy-site/package.json b/policy-site/package.json
new file mode 100644
index 00000000..7badeeab
--- /dev/null
+++ b/policy-site/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "policy-site",
+ "private": true,
+ "type": "module",
+ "version": "0.0.1",
+ "engines": {
+ "node": ">=22.18.0"
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc --noEmit && vite build",
+ "preview": "vite preview",
+ "test": "node --test \"tests/check-*.mjs\""
+ },
+ "dependencies": {
+ "@atproto/common-web": "0.5.12",
+ "@atproto/oauth-client-browser": "0.5.7",
+ "@atproto/syntax": "0.7.6",
+ "@github/combobox-nav": "3.0.2"
+ },
+ "devDependencies": {
+ "@atproto/oauth-types": "0.7.6",
+ "linkedom": "0.18.13",
+ "typescript": "7.0.2",
+ "vite": "8.3.0"
+ }
+}
diff --git a/policy-site/src/checks.ts b/policy-site/src/checks.ts
new file mode 100644
index 00000000..5da97168
--- /dev/null
+++ b/policy-site/src/checks.ts
@@ -0,0 +1,96 @@
+// The one door to the policy checks: `didbot-policy-check`, compiled to wasm
+// by scripts/build-policy-wasm.sh into public/wasm/. Every judgment this page
+// shows about a record comes through here, from the same Rust the servers
+// run. Nothing in TypeScript decides whether a record is valid.
+//
+// Loaded with a dynamic import() of a URL Vite leaves alone. The deployed
+// script-src grants 'wasm-unsafe-eval' and not 'unsafe-eval', so the module
+// must be fetched as a script, never evaluated from a string. The URL is made
+// absolute because the dev server rewrites a path-only import() to ask for
+// `?import`, which it refuses for files in public/.
+
+const MODULE_URL = "/wasm/policy-check.js";
+
+export type Severity = "error" | "warning";
+
+export interface Problem {
+ severity: Severity;
+ path: string;
+ message: string;
+}
+
+export interface Report {
+ ok: boolean;
+ problems: Problem[];
+}
+
+export interface RevisionProblem extends Problem {
+ collection: string;
+ rkey: string;
+}
+
+export interface RevisionReport {
+ digest: string | null;
+ problems: RevisionProblem[];
+}
+
+export interface Vocabulary {
+ actions: string[];
+ engines: string[];
+ includes: string[];
+}
+
+export interface Example {
+ id: string;
+ title: string;
+ collection: "bot.did.policy" | "bot.did.policyBinding";
+ rkey: string;
+ record: Record;
+}
+
+/** A record as `com.atproto.repo.listRecords` returns it, minus its URI. */
+export interface Entry {
+ rkey: string;
+ value: unknown;
+}
+
+interface Module {
+ default: () => Promise;
+ checkPolicy(record: string): string;
+ checkBinding(record: string, repo: string): string;
+ checkRevision(policies: string, bindings: string, repo: string): string;
+ vocabulary(): string;
+ examples(): string;
+}
+
+export interface Checks {
+ checkPolicy(record: unknown): Report;
+ checkBinding(record: unknown, repo: string): Report;
+ checkRevision(policies: Entry[], bindings: Entry[], repo: string): RevisionReport;
+ vocabulary: Vocabulary;
+ examples: Example[];
+}
+
+let loading: Promise | undefined;
+
+export function loadChecks(): Promise {
+ loading ??= load();
+ return loading;
+}
+
+async function load(): Promise {
+ const url = new URL(MODULE_URL, document.baseURI).href;
+ const mod = (await import(/* @vite-ignore */ url)) as Module;
+ await mod.default();
+ return {
+ checkPolicy: (record) => JSON.parse(mod.checkPolicy(JSON.stringify(record))) as Report,
+ checkBinding: (record, repo) =>
+ JSON.parse(mod.checkBinding(JSON.stringify(record), repo)) as Report,
+ checkRevision: (policies, bindings, repo) =>
+ JSON.parse(
+ mod.checkRevision(JSON.stringify(policies), JSON.stringify(bindings), repo),
+ ) as RevisionReport,
+ vocabulary: JSON.parse(mod.vocabulary()) as Vocabulary,
+ examples: JSON.parse(mod.examples()) as Example[],
+ };
+}
diff --git a/policy-site/src/copy.ts b/policy-site/src/copy.ts
new file mode 100644
index 00000000..b742daad
--- /dev/null
+++ b/policy-site/src/copy.ts
@@ -0,0 +1,23 @@
+// Every sentence of prose on this page, and the only place any of it lives.
+//
+// Per CLAUDE.md's Copywriting section, an agent does not write brand voice or
+// explanatory prose. Each string here is invented filler in ⟦…⟧ until a human
+// writes the real line: real but obscure words, so it never reads as a draft.
+//
+// grep -rn 'PH(' policy-site/src/ # in the source
+// grep -rn '⟦' policy-site/dist # in the build
+//
+// Short functional labels ("Sign in", "Save") are written where they are used.
+export const PH = (text: string) => `⟦${text}⟧`;
+
+export const copy = {
+ title: PH("quillwort"),
+ clientName: PH("quillwort"),
+ lede: PH("marram tussock over a shingle spit"),
+ signInHint: PH("fescue and sea-holly, handle or did"),
+ serversIntro: PH("lighthouse keepers compare the lamp log"),
+ policiesIntro: PH("samphire gathered at the neap tide"),
+ bindingsIntro: PH("halyard, cleat and bowline"),
+ examplesIntro: PH("specimen drawers in the herbarium"),
+ lastWriteIntro: PH("the tide table, read back"),
+} as const;
diff --git a/policy-site/src/dom.ts b/policy-site/src/dom.ts
new file mode 100644
index 00000000..26597825
--- /dev/null
+++ b/policy-site/src/dom.ts
@@ -0,0 +1,66 @@
+// Element construction for every view on the page. Strings become text nodes,
+// never markup: record contents, handles and server responses are untrusted,
+// and this is the only path by which any of them reaches the document.
+
+type Child = Node | string | null | undefined | false;
+
+export interface Props {
+ class?: string;
+ id?: string;
+ hidden?: boolean;
+ attrs?: Record;
+ on?: { [K in keyof HTMLElementEventMap]?: (event: HTMLElementEventMap[K]) => void };
+}
+
+export function h(
+ tag: K,
+ props: Props = {},
+ ...children: Child[]
+): HTMLElementTagNameMap[K] {
+ const el = document.createElement(tag);
+ if (props.class) el.className = props.class;
+ if (props.id) el.id = props.id;
+ if (props.hidden) el.hidden = true;
+ for (const [name, value] of Object.entries(props.attrs ?? {})) el.setAttribute(name, value);
+ for (const [type, listener] of Object.entries(props.on ?? {})) {
+ el.addEventListener(type, listener as EventListener);
+ }
+ append(el, ...children);
+ return el;
+}
+
+export function append(parent: Node, ...children: Child[]): void {
+ for (const child of children) {
+ if (child === null || child === undefined || child === false) continue;
+ parent.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
+ }
+}
+
+export function replaceChildren(parent: Element, ...children: Child[]): void {
+ parent.replaceChildren();
+ append(parent, ...children);
+}
+
+export function byId(id: string): T {
+ const el = document.getElementById(id);
+ if (!el) throw new Error(`index.html has no #${id}`);
+ return el as T;
+}
+
+/** The message an error carries, for showing verbatim. */
+export function errorText(error: unknown): string {
+ if (error instanceof Error) {
+ const name = "error" in error && typeof error.error === "string" ? error.error : error.name;
+ return name && name !== "Error" && !error.message.startsWith(name)
+ ? `${name}: ${error.message}`
+ : error.message;
+ }
+ return String(error);
+}
+
+let nextId = 0;
+/** A document-unique id for wiring labels and ARIA references. */
+export function uid(prefix: string): string {
+ nextId += 1;
+ return `${prefix}-${nextId}`;
+}
diff --git a/policy-site/src/main.ts b/policy-site/src/main.ts
new file mode 100644
index 00000000..0d25efed
--- /dev/null
+++ b/policy-site/src/main.ts
@@ -0,0 +1,51 @@
+import { loadChecks } from "./checks.ts";
+import { byId, errorText } from "./dom.ts";
+import { resume, signIn, signOut, type OAuthSession } from "./session.ts";
+import { renderExamples } from "./ui/examples.ts";
+
+function showError(id: string, error: unknown): void {
+ const el = byId(id);
+ el.textContent = errorText(error);
+ el.hidden = false;
+}
+
+function showAccount(session: OAuthSession): void {
+ byId("sign-in").hidden = true;
+ byId("account").hidden = false;
+ byId("account-did").textContent = session.did;
+ byId("sign-out").addEventListener("click", async () => {
+ try {
+ await signOut(session);
+ } finally {
+ location.reload();
+ }
+ });
+}
+
+async function main(): Promise {
+ byId("sign-in-form").addEventListener("submit", async (event) => {
+ event.preventDefault();
+ byId("sign-in-error").hidden = true;
+ const input = byId("sign-in-input");
+ try {
+ await signIn(input.value);
+ } catch (error) {
+ showError("sign-in-error", error);
+ }
+ });
+
+ loadChecks().then(
+ (loaded) => renderExamples(byId("example-list"), loaded.examples, null),
+ (error: unknown) => showError("page-status", `The policy checks did not load: ${errorText(error)}`),
+ );
+
+ let session: OAuthSession | undefined;
+ try {
+ session = await resume();
+ } catch (error) {
+ showError("sign-in-error", error);
+ }
+ if (session) showAccount(session);
+}
+
+void main();
diff --git a/policy-site/src/oauth-config.ts b/policy-site/src/oauth-config.ts
new file mode 100644
index 00000000..09d0404a
--- /dev/null
+++ b/policy-site/src/oauth-config.ts
@@ -0,0 +1,29 @@
+// The one place this client's OAuth identity is written down. The served
+// /client-metadata.json (see vite.config.ts), the scope the page requests and
+// the redirect it expects are all read from here, so they cannot disagree.
+
+import type { OAuthClientMetadataInput } from "@atproto/oauth-client-browser";
+import { copy } from "./copy.ts";
+
+export const ORIGIN = "https://policy.did.bot";
+
+export const CLIENT_ID = `${ORIGIN}/client-metadata.json`;
+
+export const REDIRECT_URI = `${ORIGIN}/`;
+
+// Writes to the two collections this page edits. Reads of the operator's
+// repository are public and need no scope.
+export const SCOPE = "atproto repo:bot.did.policy repo:bot.did.policyBinding";
+
+export const clientMetadata: OAuthClientMetadataInput = {
+ client_id: CLIENT_ID,
+ client_name: copy.clientName,
+ client_uri: ORIGIN,
+ application_type: "web",
+ token_endpoint_auth_method: "none",
+ dpop_bound_access_tokens: true,
+ grant_types: ["authorization_code", "refresh_token"],
+ response_types: ["code"],
+ scope: SCOPE,
+ redirect_uris: [REDIRECT_URI],
+};
diff --git a/policy-site/src/session.ts b/policy-site/src/session.ts
new file mode 100644
index 00000000..c37c8c19
--- /dev/null
+++ b/policy-site/src/session.ts
@@ -0,0 +1,70 @@
+// Signing in: a public OAuth client with DPoP and PKCE, its keys and tokens in
+// the library's own IndexedDB store.
+
+import {
+ AtprotoDohHandleResolver,
+ BrowserOAuthClient,
+ buildAtprotoLoopbackClientMetadata,
+ isLoopbackHost,
+ type HandleResolver,
+ type OAuthSession,
+} from "@atproto/oauth-client-browser";
+import { isValidDid } from "@atproto/syntax";
+import { clientMetadata, SCOPE } from "./oauth-config.ts";
+
+export type { OAuthSession };
+
+// Who sees what at sign-in:
+// - A handle is resolved over DNS-over-HTTPS at Cloudflare (1.1.1.1), which
+// sees the name `_atproto.` and the browser's address. The handle's
+// own domain is asked for /.well-known/atproto-did at the same time.
+// - A DID skips both. The OAuth library would otherwise look up the handle in
+// the DID document to verify it, so it is given a resolver that never
+// answers; the page shows the handle the operator's own PDS reports instead.
+// - Every sign-in then resolves the DID document: at plc.directory for
+// did:plc, or at the DID's own domain for did:web.
+const DOH_ENDPOINT = "https://cloudflare-dns.com/dns-query";
+
+const handles = new AtprotoDohHandleResolver({ dohEndpoint: DOH_ENDPOINT });
+
+const unverifiedHandles: HandleResolver = { resolve: async () => null };
+
+// Served from a loopback address, the page signs in as the atproto loopback
+// client: the same scope, redirected back to wherever the page is.
+// See https://atproto.com/specs/oauth#localhost-client-development.
+function metadataForThisOrigin() {
+ if (!isLoopbackHost(location.hostname)) return clientMetadata;
+ const port = location.port ? `:${location.port}` : "";
+ return buildAtprotoLoopbackClientMetadata({
+ scope: SCOPE,
+ redirect_uris: [`http://127.0.0.1${port}/`],
+ });
+}
+
+export const client = new BrowserOAuthClient({
+ clientMetadata: metadataForThisOrigin(),
+ handleResolver: unverifiedHandles,
+});
+
+/** Restores a stored session, or completes a sign-in the URL is returning from. */
+export async function resume(): Promise {
+ return (await client.init())?.session;
+}
+
+/** Leaves the page for the operator's authorization server. */
+export async function signIn(identity: string): Promise {
+ const input = identity.trim().replace(/^@/, "");
+ const did = isValidDid(input) ? input : await handles.resolve(input.toLowerCase());
+ if (!did) throw new Error(`${input} does not resolve to a DID`);
+ await client.signIn(did, { scope: SCOPE });
+}
+
+/** Revokes the session's tokens at its authorization server and forgets it. */
+export async function signOut(session: OAuthSession): Promise {
+ await session.signOut();
+}
+
+/** The PDS the session's tokens are for. */
+export async function pdsUrl(session: OAuthSession): Promise {
+ return (await session.getTokenInfo()).aud;
+}
diff --git a/policy-site/src/style.css b/policy-site/src/style.css
new file mode 100644
index 00000000..cbdfe20b
--- /dev/null
+++ b/policy-site/src/style.css
@@ -0,0 +1,321 @@
+:root {
+ color-scheme: light dark;
+ --fg: #1b1d21;
+ --muted: #5d6470;
+ --bg: #fbfbfa;
+ --panel: #ffffff;
+ --line: #d9dbe0;
+ --accent: #2657c9;
+ --ok: #1d7a3b;
+ --warn: #8a5a00;
+ --error: #b3261e;
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+ line-height: 1.45;
+ color: var(--fg);
+ background: var(--bg);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --fg: #e6e7ea;
+ --muted: #a0a6b1;
+ --bg: #15171b;
+ --panel: #1d2026;
+ --line: #353a44;
+ --accent: #8fb0ff;
+ --ok: #6fd08f;
+ --warn: #e5b451;
+ --error: #ff8a80;
+ }
+}
+
+body {
+ margin: 0;
+ background: var(--bg);
+}
+
+.masthead,
+main {
+ max-width: 64rem;
+ margin: 0 auto;
+ padding: 1rem 1.25rem;
+}
+
+.masthead {
+ border-bottom: 1px solid var(--line);
+}
+
+h1 {
+ font-size: 1.5rem;
+ margin: 0.5rem 0;
+}
+
+h2 {
+ font-size: 1.2rem;
+ margin: 2rem 0 0.5rem;
+}
+
+h3 {
+ font-size: 1rem;
+ margin: 0 0 0.5rem;
+ overflow-wrap: anywhere;
+}
+
+.mono,
+code,
+pre,
+textarea.mono {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ font-size: 0.85rem;
+}
+
+.mono,
+code {
+ overflow-wrap: anywhere;
+}
+
+.hint {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.account {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ align-items: center;
+ justify-content: space-between;
+}
+
+dl {
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ gap: 0.2rem 0.75rem;
+ margin: 0.5rem 0;
+}
+
+dt {
+ color: var(--muted);
+}
+
+dd {
+ margin: 0;
+}
+
+.cards {
+ list-style: none;
+ padding: 0;
+ display: grid;
+ gap: 0.75rem;
+}
+
+.card,
+.editor {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ padding: 0.75rem 1rem;
+}
+
+.editor {
+ display: grid;
+ gap: 0.75rem;
+ margin: 0.75rem 0;
+ border-color: var(--accent);
+}
+
+.field {
+ display: grid;
+ gap: 0.25rem;
+}
+
+label {
+ font-weight: 600;
+}
+
+input:not([type="checkbox"]),
+select,
+textarea {
+ font: inherit;
+ color: inherit;
+ background: var(--bg);
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ padding: 0.35rem 0.5rem;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+input[readonly] {
+ color: var(--muted);
+}
+
+fieldset.checkboxes {
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem 1rem;
+}
+
+fieldset.checkboxes label {
+ font-weight: normal;
+}
+
+#sign-in-form {
+ display: grid;
+ gap: 0.5rem;
+ max-width: 28rem;
+}
+
+button {
+ font: inherit;
+ padding: 0.3rem 0.8rem;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ background: var(--panel);
+ color: inherit;
+ cursor: pointer;
+}
+
+button[type="submit"] {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+button.danger {
+ border-color: var(--error);
+ color: var(--error);
+}
+
+button.link {
+ border: none;
+ background: none;
+ padding: 0 0.25rem;
+}
+
+button:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+.buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: center;
+}
+
+.status {
+ margin: 0.25rem 0;
+ font-weight: 600;
+}
+
+.status.ok,
+.status.matching {
+ color: var(--ok);
+}
+
+.status.behind,
+.status.checking {
+ color: var(--warn);
+}
+
+.status.error,
+.status.unreachable {
+ color: var(--error);
+}
+
+.problems {
+ margin: 0.25rem 0;
+ padding-left: 1.25rem;
+}
+
+.problems .severity {
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+}
+
+.problems .error .severity {
+ color: var(--error);
+}
+
+.problems .warning .severity {
+ color: var(--warn);
+}
+
+pre.verbatim {
+ background: var(--bg);
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ padding: 0.5rem;
+ overflow-x: auto;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ max-height: 24rem;
+}
+
+.picker {
+ display: grid;
+ gap: 0.25rem;
+ position: relative;
+}
+
+.chips {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+}
+
+.chip {
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ padding: 0.1rem 0.25rem 0.1rem 0.6rem;
+ background: var(--bg);
+}
+
+.detail {
+ color: var(--muted);
+ font-size: 0.85rem;
+}
+
+.suggestions {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ background: var(--panel);
+ max-height: 16rem;
+ overflow-y: auto;
+}
+
+.suggestions [role="option"] {
+ padding: 0.3rem 0.5rem;
+ cursor: pointer;
+ overflow-wrap: anywhere;
+}
+
+.suggestions [aria-selected="true"],
+.suggestions [data-combobox-option-default="true"] {
+ background: var(--accent);
+ color: var(--panel);
+}
+
+.suggestions [aria-selected="true"] .detail,
+.suggestions [data-combobox-option-default="true"] .detail {
+ color: inherit;
+}
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+[hidden] {
+ display: none !important;
+}
diff --git a/policy-site/src/ui/examples.ts b/policy-site/src/ui/examples.ts
new file mode 100644
index 00000000..a75dd8db
--- /dev/null
+++ b/policy-site/src/ui/examples.ts
@@ -0,0 +1,79 @@
+// The check module's examples, each copyable and usable as a starting point.
+
+import type { Example } from "../checks.ts";
+import { h, replaceChildren } from "../dom.ts";
+
+// Examples name their repository with the first DID, inside AT-URIs, and a
+// subject with the second.
+const EXAMPLE_OPERATOR = "did:example:operator";
+const EXAMPLE_SUBJECT = "did:example:subject";
+
+/**
+ * An example record as a starting point for the operator `did`: their DID
+ * in place of the example operator's, and no example subject left in
+ * `subjects` or `excludes`. A binding then starts with no subjects, which
+ * checkBinding refuses until the operator names one.
+ */
+export function asOperator(record: Record, did: string): Record {
+ const out = JSON.parse(JSON.stringify(record).replaceAll(EXAMPLE_OPERATOR, did)) as Record;
+ for (const field of ["subjects", "excludes"]) {
+ const dids = out[field];
+ if (Array.isArray(dids)) out[field] = dids.filter((d) => d !== EXAMPLE_SUBJECT);
+ }
+ return out;
+}
+
+export function renderExamples(
+ into: HTMLElement,
+ examples: Example[],
+ use: ((example: Example) => void) | null,
+): void {
+ replaceChildren(
+ into,
+ ...examples.map((example) => {
+ const json = JSON.stringify(example.record, null, 2);
+ const copied = h("span", { class: "hint", attrs: { role: "status" } });
+ return h(
+ "li",
+ { class: "card" },
+ h("h3", {}, example.title),
+ h(
+ "dl",
+ {},
+ h("dt", {}, "collection"),
+ h("dd", { class: "mono" }, example.collection),
+ h("dt", {}, "rkey"),
+ h("dd", { class: "mono" }, example.rkey),
+ ),
+ h("pre", { class: "verbatim" }, json),
+ h(
+ "div",
+ { class: "buttons" },
+ h(
+ "button",
+ {
+ attrs: { type: "button" },
+ on: {
+ click: () =>
+ navigator.clipboard.writeText(json).then(
+ () => (copied.textContent = "Copied"),
+ (error: unknown) => (copied.textContent = `Copy failed: ${String(error)}`),
+ ),
+ },
+ },
+ "Copy JSON",
+ ),
+ h(
+ "button",
+ {
+ attrs: use ? { type: "button" } : { type: "button", disabled: "" },
+ on: { click: () => use?.(example) },
+ },
+ example.collection === "bot.did.policy" ? "Use as new policy" : "Use as new binding",
+ ),
+ copied,
+ ),
+ );
+ }),
+ );
+}
diff --git a/policy-site/tests/check-client-metadata.mjs b/policy-site/tests/check-client-metadata.mjs
new file mode 100644
index 00000000..c2dac91e
--- /dev/null
+++ b/policy-site/tests/check-client-metadata.mjs
@@ -0,0 +1,66 @@
+// The client metadata a PDS fetches is valid, is served where its client_id
+// says, and asks for exactly what the page asks for.
+//
+// An authorization server refuses a request whose scope or redirect_uri the
+// metadata does not cover, so a mismatch is a sign-in that fails for every
+// operator. A scope wider than the page needs is worse: it is a token that
+// could write anything in an operator's repository.
+import { readFileSync } from "node:fs";
+import { isDeepStrictEqual } from "node:util";
+import { join } from "node:path";
+import { assertOAuthDiscoverableClientId, oauthClientMetadataSchema } from "@atproto/oauth-types";
+import { clientMetadata, REDIRECT_URI, SCOPE } from "../src/oauth-config.ts";
+import { distDir, fail, report, requireDist } from "./lib.mjs";
+
+// Pinned here, apart from src/oauth-config.ts, so widening it takes two edits.
+const EXPECTED_SCOPE = "atproto repo:bot.did.policy repo:bot.did.policyBinding";
+
+requireDist();
+
+let served;
+try {
+ served = JSON.parse(readFileSync(join(distDir, "client-metadata.json"), "utf8"));
+} catch (error) {
+ fail(`dist/client-metadata.json is missing or not JSON: ${error.message}`);
+}
+
+if (served) {
+ const parsed = oauthClientMetadataSchema.safeParse(served);
+ if (!parsed.success) fail(`client-metadata.json is not valid client metadata: ${parsed.error.message}`);
+
+ try {
+ assertOAuthDiscoverableClientId(served.client_id);
+ } catch (error) {
+ fail(`client_id ${served.client_id} is not a discoverable client id: ${error.message}`);
+ }
+ const clientId = new URL(served.client_id);
+ if (clientId.pathname !== "/client-metadata.json") {
+ fail(`client_id ${served.client_id} is not where the build serves the file`);
+ }
+ for (const uri of served.redirect_uris ?? []) {
+ if (new URL(uri).origin !== clientId.origin) fail(`redirect_uri ${uri} is not on ${clientId.origin}`);
+ }
+
+ if (!isDeepStrictEqual(served, JSON.parse(JSON.stringify(clientMetadata)))) {
+ fail("dist/client-metadata.json differs from src/oauth-config.ts's clientMetadata; rebuild");
+ }
+ if (served.scope !== SCOPE) fail(`served scope "${served.scope}" is not the scope the page requests, "${SCOPE}"`);
+ if (!served.redirect_uris?.includes(REDIRECT_URI)) {
+ fail(`served redirect_uris ${JSON.stringify(served.redirect_uris)} lack the page's ${REDIRECT_URI}`);
+ }
+ if (served.scope !== EXPECTED_SCOPE) fail(`scope is "${served.scope}", expected exactly "${EXPECTED_SCOPE}"`);
+
+ const required = {
+ application_type: "web",
+ token_endpoint_auth_method: "none",
+ dpop_bound_access_tokens: true,
+ };
+ for (const [key, value] of Object.entries(required)) {
+ if (served[key] !== value) fail(`${key} is ${JSON.stringify(served[key])}, expected ${JSON.stringify(value)}`);
+ }
+ for (const grant of ["authorization_code", "refresh_token"]) {
+ if (!served.grant_types?.includes(grant)) fail(`grant_types lacks ${grant}`);
+ }
+}
+
+report("client-metadata");
diff --git a/policy-site/tests/check-html.mjs b/policy-site/tests/check-html.mjs
new file mode 100644
index 00000000..09bd95b2
--- /dev/null
+++ b/policy-site/tests/check-html.mjs
@@ -0,0 +1,56 @@
+// The built pages hold nothing the deployed CSP would refuse and load nothing
+// from any origin but their own.
+//
+// script-src and style-src grant 'self' and no 'unsafe-inline': an inline
+//