feat(docs): add SvelteKit documentation site with content
Set up SvelteKit static site with mdsvex for the docs, including P0/P1 documentation pages, CI workflow, and example configs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Yechan Kim committed
Apr 9, 2026 at 17:52 UTC
d320d95d16545b467bdc97e759ed6b6584eb047d
38 files changed
+4949
-1
.github/workflows/docs.yml
new
+42
@@ -0,0 +1,42 @@
1
+name: Deploy Docs
2
+
3
+on:
4
+ push:
5
+ branches: [main]
6
+ paths: ['docs/**']
7
+ workflow_dispatch:
8
+
9
+permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+concurrency:
15
+ group: pages
16
+ cancel-in-progress: false
17
+
18
+jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+ - uses: oven-sh/setup-bun@v2
24
+ - run: bun install --frozen-lockfile
25
+ working-directory: docs
26
+ - run: bun run build
27
+ working-directory: docs
28
+ env:
29
+ BASE_PATH: ''
30
+ - uses: actions/upload-pages-artifact@v3
31
+ with:
32
+ path: docs/build
33
+
34
+ deploy:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment:
38
+ name: github-pages
39
+ url: ${{ steps.deployment.outputs.page_url }}
40
+ steps:
41
+ - id: deployment
42
+ uses: actions/deploy-pages@v4
.gitignore
+1
@@ -139,3 +139,4 @@ fabric.properties
139
!.idea/runConfigurations
140
141
# End of https://www.toptal.com/developers/gitignore/api/goland+all
142
+.gstack/
Makefile
+8
-1
@@ -1,4 +1,4 @@
1
-.PHONY: help install fmt vet lint lint-auto test vuln tidy all run build build-frontend build-tunnel build-server clean
1
+.PHONY: help install fmt vet lint lint-auto test vuln tidy all run build build-frontend build-docs build-tunnel build-server clean
2
3
.DEFAULT_GOAL := help
4
@@ -17,6 +17,7 @@ help:
17
@echo " make lint-auto - Run autofix lint/format pipeline"
18
@echo " make build - Build everything (frontend, tunnel, server)"
19
@echo " make build-frontend - Build React frontend (Tailwind CSS 4)"
20
+ @echo " make build-docs - Build documentation site (SvelteKit)"
21
@echo " make build-tunnel - Build portal-tunnel binaries"
22
@echo " make build-server - Build Go relay server (frontend built separately)"
23
@echo " make run - Run relay server"
@@ -68,6 +69,12 @@ build-frontend:
69
@cd frontend && npm i && npm run build
70
@echo "[frontend] build complete"
71
72
+# Build documentation site
73
+build-docs:
74
+ @echo "[docs] building documentation site..."
75
+ @cd docs && bun install --frozen-lockfile && bun run build
76
+ @echo "[docs] build complete"
77
+
78
# Build portal-tunnel binaries for installer distribution
79
build-tunnel:
80
@echo "[tunnel] building portal-tunnel binaries..."
docs/.gitignore
new
+3
@@ -0,0 +1,3 @@
1
+node_modules/
2
+build/
3
+.svelte-kit/
docs/bun.lock
new
+289
@@ -0,0 +1,289 @@
1
+{
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "portal-docs",
7
+ "dependencies": {
8
+ "mode-watcher": "^1.1.0",
9
+ "runed": "^0.37.1",
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/adapter-static": "^3.0.0",
13
+ "@sveltejs/kit": "^2.0.0",
14
+ "@tailwindcss/typography": "^0.5.0",
15
+ "@tailwindcss/vite": "^4.0.0",
16
+ "mdsvex": "^0.12.0",
17
+ "svelte": "^5.0.0",
18
+ "tailwindcss": "^4.0.0",
19
+ "typescript": "^5.0.0",
20
+ "vite": "^8.0.0",
21
+ },
22
+ },
23
+ },
24
+ "packages": {
25
+ "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
26
+
27
+ "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
28
+
29
+ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
30
+
31
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
32
+
33
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
34
+
35
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
36
+
37
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
38
+
39
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
40
+
41
+ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
42
+
43
+ "@oxc-project/types": ["@oxc-project/types@0.123.0", "", {}, "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew=="],
44
+
45
+ "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
46
+
47
+ "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.13", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g=="],
48
+
49
+ "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA=="],
50
+
51
+ "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug=="],
52
+
53
+ "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA=="],
54
+
55
+ "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.13", "", { "os": "linux", "cpu": "arm" }, "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw=="],
56
+
57
+ "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg=="],
58
+
59
+ "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA=="],
60
+
61
+ "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.13", "", { "os": "linux", "cpu": "ppc64" }, "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ=="],
62
+
63
+ "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.13", "", { "os": "linux", "cpu": "s390x" }, "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg=="],
64
+
65
+ "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.13", "", { "os": "linux", "cpu": "x64" }, "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA=="],
66
+
67
+ "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.13", "", { "os": "linux", "cpu": "x64" }, "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w=="],
68
+
69
+ "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.13", "", { "os": "none", "cpu": "arm64" }, "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w=="],
70
+
71
+ "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.13", "", { "dependencies": { "@emnapi/core": "1.9.1", "@emnapi/runtime": "1.9.1", "@napi-rs/wasm-runtime": "^1.1.2" }, "cpu": "none" }, "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g=="],
72
+
73
+ "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ=="],
74
+
75
+ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.13", "", { "os": "win32", "cpu": "x64" }, "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ=="],
76
+
77
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.13", "", {}, "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA=="],
78
+
79
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
80
+
81
+ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
82
+
83
+ "@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="],
84
+
85
+ "@sveltejs/kit": ["@sveltejs/kit@2.57.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.4", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-TMiqCTy9ZW4KBHvmTgeWU/hF6jcFpeMgR+9ekE06uhhGnbUZ7wpIY6l1Uk4ThRzlWYJnCVfzmtVNaHaDjaSiSg=="],
86
+
87
+ "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.0.0", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g=="],
88
+
89
+ "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
90
+
91
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
92
+
93
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
94
+
95
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
96
+
97
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
98
+
99
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
100
+
101
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
102
+
103
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
104
+
105
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
106
+
107
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
108
+
109
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
110
+
111
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "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" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
112
+
113
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
114
+
115
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
116
+
117
+ "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
118
+
119
+ "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
120
+
121
+ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
122
+
123
+ "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
124
+
125
+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
126
+
127
+ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
128
+
129
+ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
130
+
131
+ "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
132
+
133
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="],
134
+
135
+ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
136
+
137
+ "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
138
+
139
+ "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
140
+
141
+ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
142
+
143
+ "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
144
+
145
+ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
146
+
147
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
148
+
149
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
150
+
151
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
152
+
153
+ "devalue": ["devalue@5.7.0", "", {}, "sha512-qCvc8m7cImp1QDCsiY+C2EdSBWSj7Ucfoq87scSdYboDiIKdvMtFbH1U2VReBls6WMhMaUOoK3ZJEDNG/7zm3w=="],
154
+
155
+ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
156
+
157
+ "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
158
+
159
+ "esrap": ["esrap@2.2.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@typescript-eslint/types": "^8.2.0" } }, "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg=="],
160
+
161
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
162
+
163
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
164
+
165
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
166
+
167
+ "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
168
+
169
+ "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
170
+
171
+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
172
+
173
+ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
174
+
175
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
176
+
177
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
178
+
179
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
180
+
181
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
182
+
183
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
184
+
185
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
186
+
187
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
188
+
189
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
190
+
191
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
192
+
193
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
194
+
195
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
196
+
197
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
198
+
199
+ "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
200
+
201
+ "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
202
+
203
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
204
+
205
+ "mdsvex": ["mdsvex@0.12.7", "", { "dependencies": { "@types/mdast": "^4.0.4", "@types/unist": "^2.0.3", "prism-svelte": "^0.4.7", "prismjs": "^1.17.1", "unist-util-visit": "^2.0.1", "vfile-message": "^2.0.4" }, "peerDependencies": { "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120" } }, "sha512-gx4bReLCUvq+MPErHXYeyX+TEq1hsS2KfiZtEOMNTcbibSouFy8AHc5h04KbGCl+g5tLuo4/lbgRVYRnc7bJZw=="],
206
+
207
+ "mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
208
+
209
+ "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
210
+
211
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
212
+
213
+ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
214
+
215
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
216
+
217
+ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
218
+
219
+ "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
220
+
221
+ "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
222
+
223
+ "prism-svelte": ["prism-svelte@0.4.7", "", {}, "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ=="],
224
+
225
+ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
226
+
227
+ "rolldown": ["rolldown@1.0.0-rc.13", "", { "dependencies": { "@oxc-project/types": "=0.123.0", "@rolldown/pluginutils": "1.0.0-rc.13" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.13", "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", "@rolldown/binding-darwin-x64": "1.0.0-rc.13", "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw=="],
228
+
229
+ "runed": ["runed@0.37.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0", "zod": "^4.1.0" }, "optionalPeers": ["@sveltejs/kit", "zod"] }, "sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA=="],
230
+
231
+ "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
232
+
233
+ "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
234
+
235
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
236
+
237
+ "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
238
+
239
+ "svelte": ["svelte@5.55.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw=="],
240
+
241
+ "svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
242
+
243
+ "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
244
+
245
+ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
246
+
247
+ "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
248
+
249
+ "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
250
+
251
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
252
+
253
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
254
+
255
+ "unist-util-is": ["unist-util-is@4.1.0", "", {}, "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg=="],
256
+
257
+ "unist-util-stringify-position": ["unist-util-stringify-position@2.0.3", "", { "dependencies": { "@types/unist": "^2.0.2" } }, "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g=="],
258
+
259
+ "unist-util-visit": ["unist-util-visit@2.0.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0", "unist-util-visit-parents": "^3.0.0" } }, "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q=="],
260
+
261
+ "unist-util-visit-parents": ["unist-util-visit-parents@3.1.1", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0" } }, "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg=="],
262
+
263
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
264
+
265
+ "vfile-message": ["vfile-message@2.0.4", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^2.0.0" } }, "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ=="],
266
+
267
+ "vite": ["vite@8.0.7", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.13", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw=="],
268
+
269
+ "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
270
+
271
+ "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
272
+
273
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
274
+
275
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
276
+
277
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
278
+
279
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
280
+
281
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
282
+
283
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
284
+
285
+ "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
286
+
287
+ "svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
288
+ }
289
+}
docs/package-lock.json
new
+2254
@@ -0,0 +1,2254 @@
1
+{
2
+ "name": "portal-docs",
3
+ "version": "0.0.1",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "portal-docs",
9
+ "version": "0.0.1",
10
+ "dependencies": {
11
+ "mermaid": "^11.14.0",
12
+ "mode-watcher": "^1.1.0",
13
+ "runed": "^0.37.1"
14
+ },
15
+ "devDependencies": {
16
+ "@sveltejs/adapter-static": "^3.0.0",
17
+ "@sveltejs/kit": "^2.0.0",
18
+ "@tailwindcss/typography": "^0.5.0",
19
+ "@tailwindcss/vite": "^4.0.0",
20
+ "mdsvex": "^0.12.0",
21
+ "svelte": "^5.0.0",
22
+ "tailwindcss": "^4.0.0",
23
+ "typescript": "^5.0.0",
24
+ "vite": "^8.0.0"
25
+ }
26
+ },
27
+ "node_modules/@antfu/install-pkg": {
28
+ "version": "1.1.0",
29
+ "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
30
+ "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "package-manager-detector": "^1.3.0",
34
+ "tinyexec": "^1.0.1"
35
+ },
36
+ "funding": {
37
+ "url": "https://github.com/sponsors/antfu"
38
+ }
39
+ },
40
+ "node_modules/@braintree/sanitize-url": {
41
+ "version": "7.1.2",
42
+ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
43
+ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
44
+ "license": "MIT"
45
+ },
46
+ "node_modules/@chevrotain/cst-dts-gen": {
47
+ "version": "12.0.0",
48
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz",
49
+ "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==",
50
+ "license": "Apache-2.0",
51
+ "dependencies": {
52
+ "@chevrotain/gast": "12.0.0",
53
+ "@chevrotain/types": "12.0.0"
54
+ }
55
+ },
56
+ "node_modules/@chevrotain/gast": {
57
+ "version": "12.0.0",
58
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz",
59
+ "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==",
60
+ "license": "Apache-2.0",
61
+ "dependencies": {
62
+ "@chevrotain/types": "12.0.0"
63
+ }
64
+ },
65
+ "node_modules/@chevrotain/regexp-to-ast": {
66
+ "version": "12.0.0",
67
+ "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz",
68
+ "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==",
69
+ "license": "Apache-2.0"
70
+ },
71
+ "node_modules/@chevrotain/types": {
72
+ "version": "12.0.0",
73
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz",
74
+ "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==",
75
+ "license": "Apache-2.0"
76
+ },
77
+ "node_modules/@chevrotain/utils": {
78
+ "version": "12.0.0",
79
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz",
80
+ "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==",
81
+ "license": "Apache-2.0"
82
+ },
83
+ "node_modules/@iconify/types": {
84
+ "version": "2.0.0",
85
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
86
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
87
+ "license": "MIT"
88
+ },
89
+ "node_modules/@iconify/utils": {
90
+ "version": "3.1.0",
91
+ "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz",
92
+ "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
93
+ "license": "MIT",
94
+ "dependencies": {
95
+ "@antfu/install-pkg": "^1.1.0",
96
+ "@iconify/types": "^2.0.0",
97
+ "mlly": "^1.8.0"
98
+ }
99
+ },
100
+ "node_modules/@jridgewell/gen-mapping": {
101
+ "version": "0.3.13",
102
+ "license": "MIT",
103
+ "dependencies": {
104
+ "@jridgewell/sourcemap-codec": "^1.5.0",
105
+ "@jridgewell/trace-mapping": "^0.3.24"
106
+ }
107
+ },
108
+ "node_modules/@jridgewell/remapping": {
109
+ "version": "2.3.5",
110
+ "license": "MIT",
111
+ "dependencies": {
112
+ "@jridgewell/gen-mapping": "^0.3.5",
113
+ "@jridgewell/trace-mapping": "^0.3.24"
114
+ }
115
+ },
116
+ "node_modules/@jridgewell/resolve-uri": {
117
+ "version": "3.1.2",
118
+ "license": "MIT",
119
+ "engines": {
120
+ "node": ">=6.0.0"
121
+ }
122
+ },
123
+ "node_modules/@jridgewell/sourcemap-codec": {
124
+ "version": "1.5.5",
125
+ "license": "MIT"
126
+ },
127
+ "node_modules/@jridgewell/trace-mapping": {
128
+ "version": "0.3.31",
129
+ "license": "MIT",
130
+ "dependencies": {
131
+ "@jridgewell/resolve-uri": "^3.1.0",
132
+ "@jridgewell/sourcemap-codec": "^1.4.14"
133
+ }
134
+ },
135
+ "node_modules/@mermaid-js/parser": {
136
+ "version": "1.1.0",
137
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz",
138
+ "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==",
139
+ "license": "MIT",
140
+ "dependencies": {
141
+ "langium": "^4.0.0"
142
+ }
143
+ },
144
+ "node_modules/@oxc-project/types": {
145
+ "version": "0.123.0",
146
+ "devOptional": true,
147
+ "license": "MIT",
148
+ "funding": {
149
+ "url": "https://github.com/sponsors/Boshen"
150
+ }
151
+ },
152
+ "node_modules/@polka/url": {
153
+ "version": "1.0.0-next.29",
154
+ "devOptional": true,
155
+ "license": "MIT"
156
+ },
157
+ "node_modules/@rolldown/binding-darwin-arm64": {
158
+ "version": "1.0.0-rc.13",
159
+ "cpu": [
160
+ "arm64"
161
+ ],
162
+ "dev": true,
163
+ "license": "MIT",
164
+ "optional": true,
165
+ "os": [
166
+ "darwin"
167
+ ],
168
+ "engines": {
169
+ "node": "^20.19.0 || >=22.12.0"
170
+ }
171
+ },
172
+ "node_modules/@rolldown/pluginutils": {
173
+ "version": "1.0.0-rc.13",
174
+ "devOptional": true,
175
+ "license": "MIT"
176
+ },
177
+ "node_modules/@standard-schema/spec": {
178
+ "version": "1.1.0",
179
+ "devOptional": true,
180
+ "license": "MIT"
181
+ },
182
+ "node_modules/@sveltejs/acorn-typescript": {
183
+ "version": "1.0.9",
184
+ "license": "MIT",
185
+ "peerDependencies": {
186
+ "acorn": "^8.9.0"
187
+ }
188
+ },
189
+ "node_modules/@sveltejs/adapter-static": {
190
+ "version": "3.0.10",
191
+ "dev": true,
192
+ "license": "MIT",
193
+ "peerDependencies": {
194
+ "@sveltejs/kit": "^2.0.0"
195
+ }
196
+ },
197
+ "node_modules/@sveltejs/kit": {
198
+ "version": "2.57.0",
199
+ "devOptional": true,
200
+ "license": "MIT",
201
+ "dependencies": {
202
+ "@standard-schema/spec": "^1.0.0",
203
+ "@sveltejs/acorn-typescript": "^1.0.5",
204
+ "@types/cookie": "^0.6.0",
205
+ "acorn": "^8.14.1",
206
+ "cookie": "^0.6.0",
207
+ "devalue": "^5.6.4",
208
+ "esm-env": "^1.2.2",
209
+ "kleur": "^4.1.5",
210
+ "magic-string": "^0.30.5",
211
+ "mrmime": "^2.0.0",
212
+ "set-cookie-parser": "^3.0.0",
213
+ "sirv": "^3.0.0"
214
+ },
215
+ "bin": {
216
+ "svelte-kit": "svelte-kit.js"
217
+ },
218
+ "engines": {
219
+ "node": ">=18.13"
220
+ },
221
+ "peerDependencies": {
222
+ "@opentelemetry/api": "^1.0.0",
223
+ "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
224
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
225
+ "typescript": "^5.3.3 || ^6.0.0",
226
+ "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
227
+ },
228
+ "peerDependenciesMeta": {
229
+ "@opentelemetry/api": {
230
+ "optional": true
231
+ },
232
+ "typescript": {
233
+ "optional": true
234
+ }
235
+ }
236
+ },
237
+ "node_modules/@sveltejs/vite-plugin-svelte": {
238
+ "version": "7.0.0",
239
+ "devOptional": true,
240
+ "license": "MIT",
241
+ "peer": true,
242
+ "dependencies": {
243
+ "deepmerge": "^4.3.1",
244
+ "magic-string": "^0.30.21",
245
+ "obug": "^2.1.0",
246
+ "vitefu": "^1.1.2"
247
+ },
248
+ "engines": {
249
+ "node": "^20.19 || ^22.12 || >=24"
250
+ },
251
+ "peerDependencies": {
252
+ "svelte": "^5.46.4",
253
+ "vite": "^8.0.0-beta.7 || ^8.0.0"
254
+ }
255
+ },
256
+ "node_modules/@tailwindcss/node": {
257
+ "version": "4.2.2",
258
+ "dev": true,
259
+ "license": "MIT",
260
+ "dependencies": {
261
+ "@jridgewell/remapping": "^2.3.5",
262
+ "enhanced-resolve": "^5.19.0",
263
+ "jiti": "^2.6.1",
264
+ "lightningcss": "1.32.0",
265
+ "magic-string": "^0.30.21",
266
+ "source-map-js": "^1.2.1",
267
+ "tailwindcss": "4.2.2"
268
+ }
269
+ },
270
+ "node_modules/@tailwindcss/oxide": {
271
+ "version": "4.2.2",
272
+ "dev": true,
273
+ "license": "MIT",
274
+ "engines": {
275
+ "node": ">= 20"
276
+ },
277
+ "optionalDependencies": {
278
+ "@tailwindcss/oxide-android-arm64": "4.2.2",
279
+ "@tailwindcss/oxide-darwin-arm64": "4.2.2",
280
+ "@tailwindcss/oxide-darwin-x64": "4.2.2",
281
+ "@tailwindcss/oxide-freebsd-x64": "4.2.2",
282
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
283
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
284
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
285
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
286
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
287
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
288
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
289
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
290
+ }
291
+ },
292
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
293
+ "version": "4.2.2",
294
+ "cpu": [
295
+ "arm64"
296
+ ],
297
+ "dev": true,
298
+ "license": "MIT",
299
+ "optional": true,
300
+ "os": [
301
+ "darwin"
302
+ ],
303
+ "engines": {
304
+ "node": ">= 20"
305
+ }
306
+ },
307
+ "node_modules/@tailwindcss/typography": {
308
+ "version": "0.5.19",
309
+ "dev": true,
310
+ "license": "MIT",
311
+ "dependencies": {
312
+ "postcss-selector-parser": "6.0.10"
313
+ },
314
+ "peerDependencies": {
315
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
316
+ }
317
+ },
318
+ "node_modules/@tailwindcss/vite": {
319
+ "version": "4.2.2",
320
+ "dev": true,
321
+ "license": "MIT",
322
+ "dependencies": {
323
+ "@tailwindcss/node": "4.2.2",
324
+ "@tailwindcss/oxide": "4.2.2",
325
+ "tailwindcss": "4.2.2"
326
+ },
327
+ "peerDependencies": {
328
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
329
+ }
330
+ },
331
+ "node_modules/@types/cookie": {
332
+ "version": "0.6.0",
333
+ "devOptional": true,
334
+ "license": "MIT"
335
+ },
336
+ "node_modules/@types/d3": {
337
+ "version": "7.4.3",
338
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
339
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
340
+ "license": "MIT",
341
+ "dependencies": {
342
+ "@types/d3-array": "*",
343
+ "@types/d3-axis": "*",
344
+ "@types/d3-brush": "*",
345
+ "@types/d3-chord": "*",
346
+ "@types/d3-color": "*",
347
+ "@types/d3-contour": "*",
348
+ "@types/d3-delaunay": "*",
349
+ "@types/d3-dispatch": "*",
350
+ "@types/d3-drag": "*",
351
+ "@types/d3-dsv": "*",
352
+ "@types/d3-ease": "*",
353
+ "@types/d3-fetch": "*",
354
+ "@types/d3-force": "*",
355
+ "@types/d3-format": "*",
356
+ "@types/d3-geo": "*",
357
+ "@types/d3-hierarchy": "*",
358
+ "@types/d3-interpolate": "*",
359
+ "@types/d3-path": "*",
360
+ "@types/d3-polygon": "*",
361
+ "@types/d3-quadtree": "*",
362
+ "@types/d3-random": "*",
363
+ "@types/d3-scale": "*",
364
+ "@types/d3-scale-chromatic": "*",
365
+ "@types/d3-selection": "*",
366
+ "@types/d3-shape": "*",
367
+ "@types/d3-time": "*",
368
+ "@types/d3-time-format": "*",
369
+ "@types/d3-timer": "*",
370
+ "@types/d3-transition": "*",
371
+ "@types/d3-zoom": "*"
372
+ }
373
+ },
374
+ "node_modules/@types/d3-array": {
375
+ "version": "3.2.2",
376
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
377
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
378
+ "license": "MIT"
379
+ },
380
+ "node_modules/@types/d3-axis": {
381
+ "version": "3.0.6",
382
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
383
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
384
+ "license": "MIT",
385
+ "dependencies": {
386
+ "@types/d3-selection": "*"
387
+ }
388
+ },
389
+ "node_modules/@types/d3-brush": {
390
+ "version": "3.0.6",
391
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
392
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
393
+ "license": "MIT",
394
+ "dependencies": {
395
+ "@types/d3-selection": "*"
396
+ }
397
+ },
398
+ "node_modules/@types/d3-chord": {
399
+ "version": "3.0.6",
400
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
401
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
402
+ "license": "MIT"
403
+ },
404
+ "node_modules/@types/d3-color": {
405
+ "version": "3.1.3",
406
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
407
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
408
+ "license": "MIT"
409
+ },
410
+ "node_modules/@types/d3-contour": {
411
+ "version": "3.0.6",
412
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
413
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
414
+ "license": "MIT",
415
+ "dependencies": {
416
+ "@types/d3-array": "*",
417
+ "@types/geojson": "*"
418
+ }
419
+ },
420
+ "node_modules/@types/d3-delaunay": {
421
+ "version": "6.0.4",
422
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
423
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
424
+ "license": "MIT"
425
+ },
426
+ "node_modules/@types/d3-dispatch": {
427
+ "version": "3.0.7",
428
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
429
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
430
+ "license": "MIT"
431
+ },
432
+ "node_modules/@types/d3-drag": {
433
+ "version": "3.0.7",
434
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
435
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
436
+ "license": "MIT",
437
+ "dependencies": {
438
+ "@types/d3-selection": "*"
439
+ }
440
+ },
441
+ "node_modules/@types/d3-dsv": {
442
+ "version": "3.0.7",
443
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
444
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
445
+ "license": "MIT"
446
+ },
447
+ "node_modules/@types/d3-ease": {
448
+ "version": "3.0.2",
449
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
450
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
451
+ "license": "MIT"
452
+ },
453
+ "node_modules/@types/d3-fetch": {
454
+ "version": "3.0.7",
455
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
456
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
457
+ "license": "MIT",
458
+ "dependencies": {
459
+ "@types/d3-dsv": "*"
460
+ }
461
+ },
462
+ "node_modules/@types/d3-force": {
463
+ "version": "3.0.10",
464
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
465
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
466
+ "license": "MIT"
467
+ },
468
+ "node_modules/@types/d3-format": {
469
+ "version": "3.0.4",
470
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
471
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
472
+ "license": "MIT"
473
+ },
474
+ "node_modules/@types/d3-geo": {
475
+ "version": "3.1.0",
476
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
477
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
478
+ "license": "MIT",
479
+ "dependencies": {
480
+ "@types/geojson": "*"
481
+ }
482
+ },
483
+ "node_modules/@types/d3-hierarchy": {
484
+ "version": "3.1.7",
485
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
486
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
487
+ "license": "MIT"
488
+ },
489
+ "node_modules/@types/d3-interpolate": {
490
+ "version": "3.0.4",
491
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
492
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
493
+ "license": "MIT",
494
+ "dependencies": {
495
+ "@types/d3-color": "*"
496
+ }
497
+ },
498
+ "node_modules/@types/d3-path": {
499
+ "version": "3.1.1",
500
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
501
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
502
+ "license": "MIT"
503
+ },
504
+ "node_modules/@types/d3-polygon": {
505
+ "version": "3.0.2",
506
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
507
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
508
+ "license": "MIT"
509
+ },
510
+ "node_modules/@types/d3-quadtree": {
511
+ "version": "3.0.6",
512
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
513
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
514
+ "license": "MIT"
515
+ },
516
+ "node_modules/@types/d3-random": {
517
+ "version": "3.0.3",
518
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
519
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
520
+ "license": "MIT"
521
+ },
522
+ "node_modules/@types/d3-scale": {
523
+ "version": "4.0.9",
524
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
525
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
526
+ "license": "MIT",
527
+ "dependencies": {
528
+ "@types/d3-time": "*"
529
+ }
530
+ },
531
+ "node_modules/@types/d3-scale-chromatic": {
532
+ "version": "3.1.0",
533
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
534
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
535
+ "license": "MIT"
536
+ },
537
+ "node_modules/@types/d3-selection": {
538
+ "version": "3.0.11",
539
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
540
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
541
+ "license": "MIT"
542
+ },
543
+ "node_modules/@types/d3-shape": {
544
+ "version": "3.1.8",
545
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
546
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
547
+ "license": "MIT",
548
+ "dependencies": {
549
+ "@types/d3-path": "*"
550
+ }
551
+ },
552
+ "node_modules/@types/d3-time": {
553
+ "version": "3.0.4",
554
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
555
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
556
+ "license": "MIT"
557
+ },
558
+ "node_modules/@types/d3-time-format": {
559
+ "version": "4.0.3",
560
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
561
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
562
+ "license": "MIT"
563
+ },
564
+ "node_modules/@types/d3-timer": {
565
+ "version": "3.0.2",
566
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
567
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
568
+ "license": "MIT"
569
+ },
570
+ "node_modules/@types/d3-transition": {
571
+ "version": "3.0.9",
572
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
573
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
574
+ "license": "MIT",
575
+ "dependencies": {
576
+ "@types/d3-selection": "*"
577
+ }
578
+ },
579
+ "node_modules/@types/d3-zoom": {
580
+ "version": "3.0.8",
581
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
582
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
583
+ "license": "MIT",
584
+ "dependencies": {
585
+ "@types/d3-interpolate": "*",
586
+ "@types/d3-selection": "*"
587
+ }
588
+ },
589
+ "node_modules/@types/estree": {
590
+ "version": "1.0.8",
591
+ "license": "MIT"
592
+ },
593
+ "node_modules/@types/geojson": {
594
+ "version": "7946.0.16",
595
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
596
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
597
+ "license": "MIT"
598
+ },
599
+ "node_modules/@types/mdast": {
600
+ "version": "4.0.4",
601
+ "dev": true,
602
+ "license": "MIT",
603
+ "dependencies": {
604
+ "@types/unist": "*"
605
+ }
606
+ },
607
+ "node_modules/@types/trusted-types": {
608
+ "version": "2.0.7",
609
+ "license": "MIT"
610
+ },
611
+ "node_modules/@types/unist": {
612
+ "version": "2.0.11",
613
+ "dev": true,
614
+ "license": "MIT"
615
+ },
616
+ "node_modules/@typescript-eslint/types": {
617
+ "version": "8.58.1",
618
+ "license": "MIT",
619
+ "engines": {
620
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
621
+ },
622
+ "funding": {
623
+ "type": "opencollective",
624
+ "url": "https://opencollective.com/typescript-eslint"
625
+ }
626
+ },
627
+ "node_modules/@upsetjs/venn.js": {
628
+ "version": "2.0.0",
629
+ "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
630
+ "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
631
+ "license": "MIT",
632
+ "optionalDependencies": {
633
+ "d3-selection": "^3.0.0",
634
+ "d3-transition": "^3.0.1"
635
+ }
636
+ },
637
+ "node_modules/acorn": {
638
+ "version": "8.16.0",
639
+ "license": "MIT",
640
+ "bin": {
641
+ "acorn": "bin/acorn"
642
+ },
643
+ "engines": {
644
+ "node": ">=0.4.0"
645
+ }
646
+ },
647
+ "node_modules/aria-query": {
648
+ "version": "5.3.1",
649
+ "license": "Apache-2.0",
650
+ "engines": {
651
+ "node": ">= 0.4"
652
+ }
653
+ },
654
+ "node_modules/axobject-query": {
655
+ "version": "4.1.0",
656
+ "license": "Apache-2.0",
657
+ "engines": {
658
+ "node": ">= 0.4"
659
+ }
660
+ },
661
+ "node_modules/chevrotain": {
662
+ "version": "12.0.0",
663
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz",
664
+ "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==",
665
+ "license": "Apache-2.0",
666
+ "dependencies": {
667
+ "@chevrotain/cst-dts-gen": "12.0.0",
668
+ "@chevrotain/gast": "12.0.0",
669
+ "@chevrotain/regexp-to-ast": "12.0.0",
670
+ "@chevrotain/types": "12.0.0",
671
+ "@chevrotain/utils": "12.0.0"
672
+ },
673
+ "engines": {
674
+ "node": ">=22.0.0"
675
+ }
676
+ },
677
+ "node_modules/chevrotain-allstar": {
678
+ "version": "0.4.1",
679
+ "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz",
680
+ "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==",
681
+ "license": "MIT",
682
+ "dependencies": {
683
+ "lodash-es": "^4.17.21"
684
+ },
685
+ "peerDependencies": {
686
+ "chevrotain": "^12.0.0"
687
+ }
688
+ },
689
+ "node_modules/clsx": {
690
+ "version": "2.1.1",
691
+ "license": "MIT",
692
+ "engines": {
693
+ "node": ">=6"
694
+ }
695
+ },
696
+ "node_modules/commander": {
697
+ "version": "7.2.0",
698
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
699
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
700
+ "license": "MIT",
701
+ "engines": {
702
+ "node": ">= 10"
703
+ }
704
+ },
705
+ "node_modules/confbox": {
706
+ "version": "0.1.8",
707
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
708
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
709
+ "license": "MIT"
710
+ },
711
+ "node_modules/cookie": {
712
+ "version": "0.6.0",
713
+ "devOptional": true,
714
+ "license": "MIT",
715
+ "engines": {
716
+ "node": ">= 0.6"
717
+ }
718
+ },
719
+ "node_modules/cose-base": {
720
+ "version": "1.0.3",
721
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
722
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
723
+ "license": "MIT",
724
+ "dependencies": {
725
+ "layout-base": "^1.0.0"
726
+ }
727
+ },
728
+ "node_modules/cssesc": {
729
+ "version": "3.0.0",
730
+ "dev": true,
731
+ "license": "MIT",
732
+ "bin": {
733
+ "cssesc": "bin/cssesc"
734
+ },
735
+ "engines": {
736
+ "node": ">=4"
737
+ }
738
+ },
739
+ "node_modules/cytoscape": {
740
+ "version": "3.33.2",
741
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz",
742
+ "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==",
743
+ "license": "MIT",
744
+ "engines": {
745
+ "node": ">=0.10"
746
+ }
747
+ },
748
+ "node_modules/cytoscape-cose-bilkent": {
749
+ "version": "4.1.0",
750
+ "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
751
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
752
+ "license": "MIT",
753
+ "dependencies": {
754
+ "cose-base": "^1.0.0"
755
+ },
756
+ "peerDependencies": {
757
+ "cytoscape": "^3.2.0"
758
+ }
759
+ },
760
+ "node_modules/cytoscape-fcose": {
761
+ "version": "2.2.0",
762
+ "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
763
+ "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
764
+ "license": "MIT",
765
+ "dependencies": {
766
+ "cose-base": "^2.2.0"
767
+ },
768
+ "peerDependencies": {
769
+ "cytoscape": "^3.2.0"
770
+ }
771
+ },
772
+ "node_modules/cytoscape-fcose/node_modules/cose-base": {
773
+ "version": "2.2.0",
774
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
775
+ "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
776
+ "license": "MIT",
777
+ "dependencies": {
778
+ "layout-base": "^2.0.0"
779
+ }
780
+ },
781
+ "node_modules/cytoscape-fcose/node_modules/layout-base": {
782
+ "version": "2.0.1",
783
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
784
+ "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
785
+ "license": "MIT"
786
+ },
787
+ "node_modules/d3": {
788
+ "version": "7.9.0",
789
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
790
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
791
+ "license": "ISC",
792
+ "dependencies": {
793
+ "d3-array": "3",
794
+ "d3-axis": "3",
795
+ "d3-brush": "3",
796
+ "d3-chord": "3",
797
+ "d3-color": "3",
798
+ "d3-contour": "4",
799
+ "d3-delaunay": "6",
800
+ "d3-dispatch": "3",
801
+ "d3-drag": "3",
802
+ "d3-dsv": "3",
803
+ "d3-ease": "3",
804
+ "d3-fetch": "3",
805
+ "d3-force": "3",
806
+ "d3-format": "3",
807
+ "d3-geo": "3",
808
+ "d3-hierarchy": "3",
809
+ "d3-interpolate": "3",
810
+ "d3-path": "3",
811
+ "d3-polygon": "3",
812
+ "d3-quadtree": "3",
813
+ "d3-random": "3",
814
+ "d3-scale": "4",
815
+ "d3-scale-chromatic": "3",
816
+ "d3-selection": "3",
817
+ "d3-shape": "3",
818
+ "d3-time": "3",
819
+ "d3-time-format": "4",
820
+ "d3-timer": "3",
821
+ "d3-transition": "3",
822
+ "d3-zoom": "3"
823
+ },
824
+ "engines": {
825
+ "node": ">=12"
826
+ }
827
+ },
828
+ "node_modules/d3-array": {
829
+ "version": "3.2.4",
830
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
831
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
832
+ "license": "ISC",
833
+ "dependencies": {
834
+ "internmap": "1 - 2"
835
+ },
836
+ "engines": {
837
+ "node": ">=12"
838
+ }
839
+ },
840
+ "node_modules/d3-axis": {
841
+ "version": "3.0.0",
842
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
843
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
844
+ "license": "ISC",
845
+ "engines": {
846
+ "node": ">=12"
847
+ }
848
+ },
849
+ "node_modules/d3-brush": {
850
+ "version": "3.0.0",
851
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
852
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
853
+ "license": "ISC",
854
+ "dependencies": {
855
+ "d3-dispatch": "1 - 3",
856
+ "d3-drag": "2 - 3",
857
+ "d3-interpolate": "1 - 3",
858
+ "d3-selection": "3",
859
+ "d3-transition": "3"
860
+ },
861
+ "engines": {
862
+ "node": ">=12"
863
+ }
864
+ },
865
+ "node_modules/d3-chord": {
866
+ "version": "3.0.1",
867
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
868
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
869
+ "license": "ISC",
870
+ "dependencies": {
871
+ "d3-path": "1 - 3"
872
+ },
873
+ "engines": {
874
+ "node": ">=12"
875
+ }
876
+ },
877
+ "node_modules/d3-color": {
878
+ "version": "3.1.0",
879
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
880
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
881
+ "license": "ISC",
882
+ "engines": {
883
+ "node": ">=12"
884
+ }
885
+ },
886
+ "node_modules/d3-contour": {
887
+ "version": "4.0.2",
888
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
889
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
890
+ "license": "ISC",
891
+ "dependencies": {
892
+ "d3-array": "^3.2.0"
893
+ },
894
+ "engines": {
895
+ "node": ">=12"
896
+ }
897
+ },
898
+ "node_modules/d3-delaunay": {
899
+ "version": "6.0.4",
900
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
901
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
902
+ "license": "ISC",
903
+ "dependencies": {
904
+ "delaunator": "5"
905
+ },
906
+ "engines": {
907
+ "node": ">=12"
908
+ }
909
+ },
910
+ "node_modules/d3-dispatch": {
911
+ "version": "3.0.1",
912
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
913
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
914
+ "license": "ISC",
915
+ "engines": {
916
+ "node": ">=12"
917
+ }
918
+ },
919
+ "node_modules/d3-drag": {
920
+ "version": "3.0.0",
921
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
922
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
923
+ "license": "ISC",
924
+ "dependencies": {
925
+ "d3-dispatch": "1 - 3",
926
+ "d3-selection": "3"
927
+ },
928
+ "engines": {
929
+ "node": ">=12"
930
+ }
931
+ },
932
+ "node_modules/d3-dsv": {
933
+ "version": "3.0.1",
934
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
935
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
936
+ "license": "ISC",
937
+ "dependencies": {
938
+ "commander": "7",
939
+ "iconv-lite": "0.6",
940
+ "rw": "1"
941
+ },
942
+ "bin": {
943
+ "csv2json": "bin/dsv2json.js",
944
+ "csv2tsv": "bin/dsv2dsv.js",
945
+ "dsv2dsv": "bin/dsv2dsv.js",
946
+ "dsv2json": "bin/dsv2json.js",
947
+ "json2csv": "bin/json2dsv.js",
948
+ "json2dsv": "bin/json2dsv.js",
949
+ "json2tsv": "bin/json2dsv.js",
950
+ "tsv2csv": "bin/dsv2dsv.js",
951
+ "tsv2json": "bin/dsv2json.js"
952
+ },
953
+ "engines": {
954
+ "node": ">=12"
955
+ }
956
+ },
957
+ "node_modules/d3-ease": {
958
+ "version": "3.0.1",
959
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
960
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
961
+ "license": "BSD-3-Clause",
962
+ "engines": {
963
+ "node": ">=12"
964
+ }
965
+ },
966
+ "node_modules/d3-fetch": {
967
+ "version": "3.0.1",
968
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
969
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
970
+ "license": "ISC",
971
+ "dependencies": {
972
+ "d3-dsv": "1 - 3"
973
+ },
974
+ "engines": {
975
+ "node": ">=12"
976
+ }
977
+ },
978
+ "node_modules/d3-force": {
979
+ "version": "3.0.0",
980
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
981
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
982
+ "license": "ISC",
983
+ "dependencies": {
984
+ "d3-dispatch": "1 - 3",
985
+ "d3-quadtree": "1 - 3",
986
+ "d3-timer": "1 - 3"
987
+ },
988
+ "engines": {
989
+ "node": ">=12"
990
+ }
991
+ },
992
+ "node_modules/d3-format": {
993
+ "version": "3.1.2",
994
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
995
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
996
+ "license": "ISC",
997
+ "engines": {
998
+ "node": ">=12"
999
+ }
1000
+ },
1001
+ "node_modules/d3-geo": {
1002
+ "version": "3.1.1",
1003
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
1004
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
1005
+ "license": "ISC",
1006
+ "dependencies": {
1007
+ "d3-array": "2.5.0 - 3"
1008
+ },
1009
+ "engines": {
1010
+ "node": ">=12"
1011
+ }
1012
+ },
1013
+ "node_modules/d3-hierarchy": {
1014
+ "version": "3.1.2",
1015
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
1016
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
1017
+ "license": "ISC",
1018
+ "engines": {
1019
+ "node": ">=12"
1020
+ }
1021
+ },
1022
+ "node_modules/d3-interpolate": {
1023
+ "version": "3.0.1",
1024
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
1025
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
1026
+ "license": "ISC",
1027
+ "dependencies": {
1028
+ "d3-color": "1 - 3"
1029
+ },
1030
+ "engines": {
1031
+ "node": ">=12"
1032
+ }
1033
+ },
1034
+ "node_modules/d3-path": {
1035
+ "version": "3.1.0",
1036
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
1037
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
1038
+ "license": "ISC",
1039
+ "engines": {
1040
+ "node": ">=12"
1041
+ }
1042
+ },
1043
+ "node_modules/d3-polygon": {
1044
+ "version": "3.0.1",
1045
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
1046
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
1047
+ "license": "ISC",
1048
+ "engines": {
1049
+ "node": ">=12"
1050
+ }
1051
+ },
1052
+ "node_modules/d3-quadtree": {
1053
+ "version": "3.0.1",
1054
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
1055
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
1056
+ "license": "ISC",
1057
+ "engines": {
1058
+ "node": ">=12"
1059
+ }
1060
+ },
1061
+ "node_modules/d3-random": {
1062
+ "version": "3.0.1",
1063
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
1064
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
1065
+ "license": "ISC",
1066
+ "engines": {
1067
+ "node": ">=12"
1068
+ }
1069
+ },
1070
+ "node_modules/d3-sankey": {
1071
+ "version": "0.12.3",
1072
+ "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
1073
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
1074
+ "license": "BSD-3-Clause",
1075
+ "dependencies": {
1076
+ "d3-array": "1 - 2",
1077
+ "d3-shape": "^1.2.0"
1078
+ }
1079
+ },
1080
+ "node_modules/d3-sankey/node_modules/d3-array": {
1081
+ "version": "2.12.1",
1082
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
1083
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
1084
+ "license": "BSD-3-Clause",
1085
+ "dependencies": {
1086
+ "internmap": "^1.0.0"
1087
+ }
1088
+ },
1089
+ "node_modules/d3-sankey/node_modules/d3-path": {
1090
+ "version": "1.0.9",
1091
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
1092
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
1093
+ "license": "BSD-3-Clause"
1094
+ },
1095
+ "node_modules/d3-sankey/node_modules/d3-shape": {
1096
+ "version": "1.3.7",
1097
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
1098
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
1099
+ "license": "BSD-3-Clause",
1100
+ "dependencies": {
1101
+ "d3-path": "1"
1102
+ }
1103
+ },
1104
+ "node_modules/d3-sankey/node_modules/internmap": {
1105
+ "version": "1.0.1",
1106
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
1107
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
1108
+ "license": "ISC"
1109
+ },
1110
+ "node_modules/d3-scale": {
1111
+ "version": "4.0.2",
1112
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
1113
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
1114
+ "license": "ISC",
1115
+ "dependencies": {
1116
+ "d3-array": "2.10.0 - 3",
1117
+ "d3-format": "1 - 3",
1118
+ "d3-interpolate": "1.2.0 - 3",
1119
+ "d3-time": "2.1.1 - 3",
1120
+ "d3-time-format": "2 - 4"
1121
+ },
1122
+ "engines": {
1123
+ "node": ">=12"
1124
+ }
1125
+ },
1126
+ "node_modules/d3-scale-chromatic": {
1127
+ "version": "3.1.0",
1128
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
1129
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
1130
+ "license": "ISC",
1131
+ "dependencies": {
1132
+ "d3-color": "1 - 3",
1133
+ "d3-interpolate": "1 - 3"
1134
+ },
1135
+ "engines": {
1136
+ "node": ">=12"
1137
+ }
1138
+ },
1139
+ "node_modules/d3-selection": {
1140
+ "version": "3.0.0",
1141
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
1142
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
1143
+ "license": "ISC",
1144
+ "engines": {
1145
+ "node": ">=12"
1146
+ }
1147
+ },
1148
+ "node_modules/d3-shape": {
1149
+ "version": "3.2.0",
1150
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
1151
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
1152
+ "license": "ISC",
1153
+ "dependencies": {
1154
+ "d3-path": "^3.1.0"
1155
+ },
1156
+ "engines": {
1157
+ "node": ">=12"
1158
+ }
1159
+ },
1160
+ "node_modules/d3-time": {
1161
+ "version": "3.1.0",
1162
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
1163
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
1164
+ "license": "ISC",
1165
+ "dependencies": {
1166
+ "d3-array": "2 - 3"
1167
+ },
1168
+ "engines": {
1169
+ "node": ">=12"
1170
+ }
1171
+ },
1172
+ "node_modules/d3-time-format": {
1173
+ "version": "4.1.0",
1174
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
1175
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
1176
+ "license": "ISC",
1177
+ "dependencies": {
1178
+ "d3-time": "1 - 3"
1179
+ },
1180
+ "engines": {
1181
+ "node": ">=12"
1182
+ }
1183
+ },
1184
+ "node_modules/d3-timer": {
1185
+ "version": "3.0.1",
1186
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
1187
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
1188
+ "license": "ISC",
1189
+ "engines": {
1190
+ "node": ">=12"
1191
+ }
1192
+ },
1193
+ "node_modules/d3-transition": {
1194
+ "version": "3.0.1",
1195
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
1196
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
1197
+ "license": "ISC",
1198
+ "dependencies": {
1199
+ "d3-color": "1 - 3",
1200
+ "d3-dispatch": "1 - 3",
1201
+ "d3-ease": "1 - 3",
1202
+ "d3-interpolate": "1 - 3",
1203
+ "d3-timer": "1 - 3"
1204
+ },
1205
+ "engines": {
1206
+ "node": ">=12"
1207
+ },
1208
+ "peerDependencies": {
1209
+ "d3-selection": "2 - 3"
1210
+ }
1211
+ },
1212
+ "node_modules/d3-zoom": {
1213
+ "version": "3.0.0",
1214
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
1215
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
1216
+ "license": "ISC",
1217
+ "dependencies": {
1218
+ "d3-dispatch": "1 - 3",
1219
+ "d3-drag": "2 - 3",
1220
+ "d3-interpolate": "1 - 3",
1221
+ "d3-selection": "2 - 3",
1222
+ "d3-transition": "2 - 3"
1223
+ },
1224
+ "engines": {
1225
+ "node": ">=12"
1226
+ }
1227
+ },
1228
+ "node_modules/dagre-d3-es": {
1229
+ "version": "7.0.14",
1230
+ "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
1231
+ "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
1232
+ "license": "MIT",
1233
+ "dependencies": {
1234
+ "d3": "^7.9.0",
1235
+ "lodash-es": "^4.17.21"
1236
+ }
1237
+ },
1238
+ "node_modules/dayjs": {
1239
+ "version": "1.11.20",
1240
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
1241
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
1242
+ "license": "MIT"
1243
+ },
1244
+ "node_modules/deepmerge": {
1245
+ "version": "4.3.1",
1246
+ "devOptional": true,
1247
+ "license": "MIT",
1248
+ "peer": true,
1249
+ "engines": {
1250
+ "node": ">=0.10.0"
1251
+ }
1252
+ },
1253
+ "node_modules/delaunator": {
1254
+ "version": "5.1.0",
1255
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
1256
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
1257
+ "license": "ISC",
1258
+ "dependencies": {
1259
+ "robust-predicates": "^3.0.2"
1260
+ }
1261
+ },
1262
+ "node_modules/dequal": {
1263
+ "version": "2.0.3",
1264
+ "license": "MIT",
1265
+ "engines": {
1266
+ "node": ">=6"
1267
+ }
1268
+ },
1269
+ "node_modules/detect-libc": {
1270
+ "version": "2.1.2",
1271
+ "devOptional": true,
1272
+ "license": "Apache-2.0",
1273
+ "engines": {
1274
+ "node": ">=8"
1275
+ }
1276
+ },
1277
+ "node_modules/devalue": {
1278
+ "version": "5.7.0",
1279
+ "license": "MIT"
1280
+ },
1281
+ "node_modules/dompurify": {
1282
+ "version": "3.3.3",
1283
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
1284
+ "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
1285
+ "license": "(MPL-2.0 OR Apache-2.0)",
1286
+ "optionalDependencies": {
1287
+ "@types/trusted-types": "^2.0.7"
1288
+ }
1289
+ },
1290
+ "node_modules/enhanced-resolve": {
1291
+ "version": "5.20.1",
1292
+ "dev": true,
1293
+ "license": "MIT",
1294
+ "dependencies": {
1295
+ "graceful-fs": "^4.2.4",
1296
+ "tapable": "^2.3.0"
1297
+ },
1298
+ "engines": {
1299
+ "node": ">=10.13.0"
1300
+ }
1301
+ },
1302
+ "node_modules/esm-env": {
1303
+ "version": "1.2.2",
1304
+ "license": "MIT"
1305
+ },
1306
+ "node_modules/esrap": {
1307
+ "version": "2.2.4",
1308
+ "license": "MIT",
1309
+ "dependencies": {
1310
+ "@jridgewell/sourcemap-codec": "^1.4.15",
1311
+ "@typescript-eslint/types": "^8.2.0"
1312
+ }
1313
+ },
1314
+ "node_modules/fdir": {
1315
+ "version": "6.5.0",
1316
+ "devOptional": true,
1317
+ "license": "MIT",
1318
+ "engines": {
1319
+ "node": ">=12.0.0"
1320
+ },
1321
+ "peerDependencies": {
1322
+ "picomatch": "^3 || ^4"
1323
+ },
1324
+ "peerDependenciesMeta": {
1325
+ "picomatch": {
1326
+ "optional": true
1327
+ }
1328
+ }
1329
+ },
1330
+ "node_modules/fsevents": {
1331
+ "version": "2.3.3",
1332
+ "dev": true,
1333
+ "license": "MIT",
1334
+ "optional": true,
1335
+ "os": [
1336
+ "darwin"
1337
+ ],
1338
+ "engines": {
1339
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1340
+ }
1341
+ },
1342
+ "node_modules/graceful-fs": {
1343
+ "version": "4.2.11",
1344
+ "dev": true,
1345
+ "license": "ISC"
1346
+ },
1347
+ "node_modules/hachure-fill": {
1348
+ "version": "0.5.2",
1349
+ "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
1350
+ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
1351
+ "license": "MIT"
1352
+ },
1353
+ "node_modules/iconv-lite": {
1354
+ "version": "0.6.3",
1355
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1356
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1357
+ "license": "MIT",
1358
+ "dependencies": {
1359
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1360
+ },
1361
+ "engines": {
1362
+ "node": ">=0.10.0"
1363
+ }
1364
+ },
1365
+ "node_modules/inline-style-parser": {
1366
+ "version": "0.2.7",
1367
+ "license": "MIT"
1368
+ },
1369
+ "node_modules/internmap": {
1370
+ "version": "2.0.3",
1371
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
1372
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
1373
+ "license": "ISC",
1374
+ "engines": {
1375
+ "node": ">=12"
1376
+ }
1377
+ },
1378
+ "node_modules/is-reference": {
1379
+ "version": "3.0.3",
1380
+ "license": "MIT",
1381
+ "dependencies": {
1382
+ "@types/estree": "^1.0.6"
1383
+ }
1384
+ },
1385
+ "node_modules/jiti": {
1386
+ "version": "2.6.1",
1387
+ "dev": true,
1388
+ "license": "MIT",
1389
+ "bin": {
1390
+ "jiti": "lib/jiti-cli.mjs"
1391
+ }
1392
+ },
1393
+ "node_modules/katex": {
1394
+ "version": "0.16.45",
1395
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz",
1396
+ "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==",
1397
+ "funding": [
1398
+ "https://opencollective.com/katex",
1399
+ "https://github.com/sponsors/katex"
1400
+ ],
1401
+ "license": "MIT",
1402
+ "dependencies": {
1403
+ "commander": "^8.3.0"
1404
+ },
1405
+ "bin": {
1406
+ "katex": "cli.js"
1407
+ }
1408
+ },
1409
+ "node_modules/katex/node_modules/commander": {
1410
+ "version": "8.3.0",
1411
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
1412
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
1413
+ "license": "MIT",
1414
+ "engines": {
1415
+ "node": ">= 12"
1416
+ }
1417
+ },
1418
+ "node_modules/khroma": {
1419
+ "version": "2.1.0",
1420
+ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
1421
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
1422
+ },
1423
+ "node_modules/kleur": {
1424
+ "version": "4.1.5",
1425
+ "devOptional": true,
1426
+ "license": "MIT",
1427
+ "engines": {
1428
+ "node": ">=6"
1429
+ }
1430
+ },
1431
+ "node_modules/langium": {
1432
+ "version": "4.2.2",
1433
+ "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz",
1434
+ "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==",
1435
+ "license": "MIT",
1436
+ "dependencies": {
1437
+ "@chevrotain/regexp-to-ast": "~12.0.0",
1438
+ "chevrotain": "~12.0.0",
1439
+ "chevrotain-allstar": "~0.4.1",
1440
+ "vscode-languageserver": "~9.0.1",
1441
+ "vscode-languageserver-textdocument": "~1.0.11",
1442
+ "vscode-uri": "~3.1.0"
1443
+ },
1444
+ "engines": {
1445
+ "node": ">=20.10.0",
1446
+ "npm": ">=10.2.3"
1447
+ }
1448
+ },
1449
+ "node_modules/layout-base": {
1450
+ "version": "1.0.2",
1451
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
1452
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
1453
+ "license": "MIT"
1454
+ },
1455
+ "node_modules/lightningcss": {
1456
+ "version": "1.32.0",
1457
+ "devOptional": true,
1458
+ "license": "MPL-2.0",
1459
+ "dependencies": {
1460
+ "detect-libc": "^2.0.3"
1461
+ },
1462
+ "engines": {
1463
+ "node": ">= 12.0.0"
1464
+ },
1465
+ "funding": {
1466
+ "type": "opencollective",
1467
+ "url": "https://opencollective.com/parcel"
1468
+ },
1469
+ "optionalDependencies": {
1470
+ "lightningcss-android-arm64": "1.32.0",
1471
+ "lightningcss-darwin-arm64": "1.32.0",
1472
+ "lightningcss-darwin-x64": "1.32.0",
1473
+ "lightningcss-freebsd-x64": "1.32.0",
1474
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
1475
+ "lightningcss-linux-arm64-gnu": "1.32.0",
1476
+ "lightningcss-linux-arm64-musl": "1.32.0",
1477
+ "lightningcss-linux-x64-gnu": "1.32.0",
1478
+ "lightningcss-linux-x64-musl": "1.32.0",
1479
+ "lightningcss-win32-arm64-msvc": "1.32.0",
1480
+ "lightningcss-win32-x64-msvc": "1.32.0"
1481
+ }
1482
+ },
1483
+ "node_modules/lightningcss-darwin-arm64": {
1484
+ "version": "1.32.0",
1485
+ "cpu": [
1486
+ "arm64"
1487
+ ],
1488
+ "dev": true,
1489
+ "license": "MPL-2.0",
1490
+ "optional": true,
1491
+ "os": [
1492
+ "darwin"
1493
+ ],
1494
+ "engines": {
1495
+ "node": ">= 12.0.0"
1496
+ },
1497
+ "funding": {
1498
+ "type": "opencollective",
1499
+ "url": "https://opencollective.com/parcel"
1500
+ }
1501
+ },
1502
+ "node_modules/locate-character": {
1503
+ "version": "3.0.0",
1504
+ "license": "MIT"
1505
+ },
1506
+ "node_modules/lodash-es": {
1507
+ "version": "4.18.1",
1508
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
1509
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
1510
+ "license": "MIT"
1511
+ },
1512
+ "node_modules/lz-string": {
1513
+ "version": "1.5.0",
1514
+ "license": "MIT",
1515
+ "bin": {
1516
+ "lz-string": "bin/bin.js"
1517
+ }
1518
+ },
1519
+ "node_modules/magic-string": {
1520
+ "version": "0.30.21",
1521
+ "license": "MIT",
1522
+ "dependencies": {
1523
+ "@jridgewell/sourcemap-codec": "^1.5.5"
1524
+ }
1525
+ },
1526
+ "node_modules/marked": {
1527
+ "version": "16.4.2",
1528
+ "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
1529
+ "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
1530
+ "license": "MIT",
1531
+ "bin": {
1532
+ "marked": "bin/marked.js"
1533
+ },
1534
+ "engines": {
1535
+ "node": ">= 20"
1536
+ }
1537
+ },
1538
+ "node_modules/mdsvex": {
1539
+ "version": "0.12.7",
1540
+ "dev": true,
1541
+ "license": "MIT",
1542
+ "dependencies": {
1543
+ "@types/mdast": "^4.0.4",
1544
+ "@types/unist": "^2.0.3",
1545
+ "prism-svelte": "^0.4.7",
1546
+ "prismjs": "^1.17.1",
1547
+ "unist-util-visit": "^2.0.1",
1548
+ "vfile-message": "^2.0.4"
1549
+ },
1550
+ "peerDependencies": {
1551
+ "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120"
1552
+ }
1553
+ },
1554
+ "node_modules/mermaid": {
1555
+ "version": "11.14.0",
1556
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
1557
+ "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
1558
+ "license": "MIT",
1559
+ "dependencies": {
1560
+ "@braintree/sanitize-url": "^7.1.1",
1561
+ "@iconify/utils": "^3.0.2",
1562
+ "@mermaid-js/parser": "^1.1.0",
1563
+ "@types/d3": "^7.4.3",
1564
+ "@upsetjs/venn.js": "^2.0.0",
1565
+ "cytoscape": "^3.33.1",
1566
+ "cytoscape-cose-bilkent": "^4.1.0",
1567
+ "cytoscape-fcose": "^2.2.0",
1568
+ "d3": "^7.9.0",
1569
+ "d3-sankey": "^0.12.3",
1570
+ "dagre-d3-es": "7.0.14",
1571
+ "dayjs": "^1.11.19",
1572
+ "dompurify": "^3.3.1",
1573
+ "katex": "^0.16.25",
1574
+ "khroma": "^2.1.0",
1575
+ "lodash-es": "^4.17.23",
1576
+ "marked": "^16.3.0",
1577
+ "roughjs": "^4.6.6",
1578
+ "stylis": "^4.3.6",
1579
+ "ts-dedent": "^2.2.0",
1580
+ "uuid": "^11.1.0"
1581
+ }
1582
+ },
1583
+ "node_modules/mlly": {
1584
+ "version": "1.8.2",
1585
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
1586
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
1587
+ "license": "MIT",
1588
+ "dependencies": {
1589
+ "acorn": "^8.16.0",
1590
+ "pathe": "^2.0.3",
1591
+ "pkg-types": "^1.3.1",
1592
+ "ufo": "^1.6.3"
1593
+ }
1594
+ },
1595
+ "node_modules/mode-watcher": {
1596
+ "version": "1.1.0",
1597
+ "license": "MIT",
1598
+ "dependencies": {
1599
+ "runed": "^0.25.0",
1600
+ "svelte-toolbelt": "^0.7.1"
1601
+ },
1602
+ "peerDependencies": {
1603
+ "svelte": "^5.27.0"
1604
+ }
1605
+ },
1606
+ "node_modules/mode-watcher/node_modules/runed": {
1607
+ "version": "0.25.0",
1608
+ "funding": [
1609
+ "https://github.com/sponsors/huntabyte",
1610
+ "https://github.com/sponsors/tglide"
1611
+ ],
1612
+ "dependencies": {
1613
+ "esm-env": "^1.0.0"
1614
+ },
1615
+ "peerDependencies": {
1616
+ "svelte": "^5.7.0"
1617
+ }
1618
+ },
1619
+ "node_modules/mrmime": {
1620
+ "version": "2.0.1",
1621
+ "devOptional": true,
1622
+ "license": "MIT",
1623
+ "engines": {
1624
+ "node": ">=10"
1625
+ }
1626
+ },
1627
+ "node_modules/nanoid": {
1628
+ "version": "3.3.11",
1629
+ "devOptional": true,
1630
+ "funding": [
1631
+ {
1632
+ "type": "github",
1633
+ "url": "https://github.com/sponsors/ai"
1634
+ }
1635
+ ],
1636
+ "license": "MIT",
1637
+ "bin": {
1638
+ "nanoid": "bin/nanoid.cjs"
1639
+ },
1640
+ "engines": {
1641
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1642
+ }
1643
+ },
1644
+ "node_modules/obug": {
1645
+ "version": "2.1.1",
1646
+ "devOptional": true,
1647
+ "funding": [
1648
+ "https://github.com/sponsors/sxzz",
1649
+ "https://opencollective.com/debug"
1650
+ ],
1651
+ "license": "MIT",
1652
+ "peer": true
1653
+ },
1654
+ "node_modules/package-manager-detector": {
1655
+ "version": "1.6.0",
1656
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
1657
+ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
1658
+ "license": "MIT"
1659
+ },
1660
+ "node_modules/path-data-parser": {
1661
+ "version": "0.1.0",
1662
+ "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz",
1663
+ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
1664
+ "license": "MIT"
1665
+ },
1666
+ "node_modules/pathe": {
1667
+ "version": "2.0.3",
1668
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
1669
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
1670
+ "license": "MIT"
1671
+ },
1672
+ "node_modules/picocolors": {
1673
+ "version": "1.1.1",
1674
+ "devOptional": true,
1675
+ "license": "ISC"
1676
+ },
1677
+ "node_modules/picomatch": {
1678
+ "version": "4.0.4",
1679
+ "devOptional": true,
1680
+ "license": "MIT",
1681
+ "engines": {
1682
+ "node": ">=12"
1683
+ },
1684
+ "funding": {
1685
+ "url": "https://github.com/sponsors/jonschlinkert"
1686
+ }
1687
+ },
1688
+ "node_modules/pkg-types": {
1689
+ "version": "1.3.1",
1690
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
1691
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
1692
+ "license": "MIT",
1693
+ "dependencies": {
1694
+ "confbox": "^0.1.8",
1695
+ "mlly": "^1.7.4",
1696
+ "pathe": "^2.0.1"
1697
+ }
1698
+ },
1699
+ "node_modules/points-on-curve": {
1700
+ "version": "0.2.0",
1701
+ "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
1702
+ "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
1703
+ "license": "MIT"
1704
+ },
1705
+ "node_modules/points-on-path": {
1706
+ "version": "0.2.1",
1707
+ "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz",
1708
+ "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
1709
+ "license": "MIT",
1710
+ "dependencies": {
1711
+ "path-data-parser": "0.1.0",
1712
+ "points-on-curve": "0.2.0"
1713
+ }
1714
+ },
1715
+ "node_modules/postcss": {
1716
+ "version": "8.5.9",
1717
+ "devOptional": true,
1718
+ "funding": [
1719
+ {
1720
+ "type": "opencollective",
1721
+ "url": "https://opencollective.com/postcss/"
1722
+ },
1723
+ {
1724
+ "type": "tidelift",
1725
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1726
+ },
1727
+ {
1728
+ "type": "github",
1729
+ "url": "https://github.com/sponsors/ai"
1730
+ }
1731
+ ],
1732
+ "license": "MIT",
1733
+ "dependencies": {
1734
+ "nanoid": "^3.3.11",
1735
+ "picocolors": "^1.1.1",
1736
+ "source-map-js": "^1.2.1"
1737
+ },
1738
+ "engines": {
1739
+ "node": "^10 || ^12 || >=14"
1740
+ }
1741
+ },
1742
+ "node_modules/postcss-selector-parser": {
1743
+ "version": "6.0.10",
1744
+ "dev": true,
1745
+ "license": "MIT",
1746
+ "dependencies": {
1747
+ "cssesc": "^3.0.0",
1748
+ "util-deprecate": "^1.0.2"
1749
+ },
1750
+ "engines": {
1751
+ "node": ">=4"
1752
+ }
1753
+ },
1754
+ "node_modules/prism-svelte": {
1755
+ "version": "0.4.7",
1756
+ "dev": true,
1757
+ "license": "MIT"
1758
+ },
1759
+ "node_modules/prismjs": {
1760
+ "version": "1.30.0",
1761
+ "dev": true,
1762
+ "license": "MIT",
1763
+ "engines": {
1764
+ "node": ">=6"
1765
+ }
1766
+ },
1767
+ "node_modules/robust-predicates": {
1768
+ "version": "3.0.3",
1769
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
1770
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
1771
+ "license": "Unlicense"
1772
+ },
1773
+ "node_modules/rolldown": {
1774
+ "version": "1.0.0-rc.13",
1775
+ "devOptional": true,
1776
+ "license": "MIT",
1777
+ "dependencies": {
1778
+ "@oxc-project/types": "=0.123.0",
1779
+ "@rolldown/pluginutils": "1.0.0-rc.13"
1780
+ },
1781
+ "bin": {
1782
+ "rolldown": "bin/cli.mjs"
1783
+ },
1784
+ "engines": {
1785
+ "node": "^20.19.0 || >=22.12.0"
1786
+ },
1787
+ "optionalDependencies": {
1788
+ "@rolldown/binding-android-arm64": "1.0.0-rc.13",
1789
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.13",
1790
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.13",
1791
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.13",
1792
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13",
1793
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13",
1794
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13",
1795
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13",
1796
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13",
1797
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13",
1798
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13",
1799
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13",
1800
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13",
1801
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13",
1802
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13"
1803
+ }
1804
+ },
1805
+ "node_modules/roughjs": {
1806
+ "version": "4.6.6",
1807
+ "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
1808
+ "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
1809
+ "license": "MIT",
1810
+ "dependencies": {
1811
+ "hachure-fill": "^0.5.2",
1812
+ "path-data-parser": "^0.1.0",
1813
+ "points-on-curve": "^0.2.0",
1814
+ "points-on-path": "^0.2.1"
1815
+ }
1816
+ },
1817
+ "node_modules/runed": {
1818
+ "version": "0.37.1",
1819
+ "funding": [
1820
+ "https://github.com/sponsors/huntabyte",
1821
+ "https://github.com/sponsors/tglide"
1822
+ ],
1823
+ "license": "MIT",
1824
+ "dependencies": {
1825
+ "dequal": "^2.0.3",
1826
+ "esm-env": "^1.0.0",
1827
+ "lz-string": "^1.5.0"
1828
+ },
1829
+ "peerDependencies": {
1830
+ "@sveltejs/kit": "^2.21.0",
1831
+ "svelte": "^5.7.0",
1832
+ "zod": "^4.1.0"
1833
+ },
1834
+ "peerDependenciesMeta": {
1835
+ "@sveltejs/kit": {
1836
+ "optional": true
1837
+ },
1838
+ "zod": {
1839
+ "optional": true
1840
+ }
1841
+ }
1842
+ },
1843
+ "node_modules/rw": {
1844
+ "version": "1.3.3",
1845
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
1846
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
1847
+ "license": "BSD-3-Clause"
1848
+ },
1849
+ "node_modules/safer-buffer": {
1850
+ "version": "2.1.2",
1851
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1852
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1853
+ "license": "MIT"
1854
+ },
1855
+ "node_modules/set-cookie-parser": {
1856
+ "version": "3.1.0",
1857
+ "devOptional": true,
1858
+ "license": "MIT"
1859
+ },
1860
+ "node_modules/sirv": {
1861
+ "version": "3.0.2",
1862
+ "devOptional": true,
1863
+ "license": "MIT",
1864
+ "dependencies": {
1865
+ "@polka/url": "^1.0.0-next.24",
1866
+ "mrmime": "^2.0.0",
1867
+ "totalist": "^3.0.0"
1868
+ },
1869
+ "engines": {
1870
+ "node": ">=18"
1871
+ }
1872
+ },
1873
+ "node_modules/source-map-js": {
1874
+ "version": "1.2.1",
1875
+ "devOptional": true,
1876
+ "license": "BSD-3-Clause",
1877
+ "engines": {
1878
+ "node": ">=0.10.0"
1879
+ }
1880
+ },
1881
+ "node_modules/style-to-object": {
1882
+ "version": "1.0.14",
1883
+ "license": "MIT",
1884
+ "dependencies": {
1885
+ "inline-style-parser": "0.2.7"
1886
+ }
1887
+ },
1888
+ "node_modules/stylis": {
1889
+ "version": "4.3.6",
1890
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
1891
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
1892
+ "license": "MIT"
1893
+ },
1894
+ "node_modules/svelte": {
1895
+ "version": "5.55.2",
1896
+ "license": "MIT",
1897
+ "dependencies": {
1898
+ "@jridgewell/remapping": "^2.3.4",
1899
+ "@jridgewell/sourcemap-codec": "^1.5.0",
1900
+ "@sveltejs/acorn-typescript": "^1.0.5",
1901
+ "@types/estree": "^1.0.5",
1902
+ "@types/trusted-types": "^2.0.7",
1903
+ "acorn": "^8.12.1",
1904
+ "aria-query": "5.3.1",
1905
+ "axobject-query": "^4.1.0",
1906
+ "clsx": "^2.1.1",
1907
+ "devalue": "^5.6.4",
1908
+ "esm-env": "^1.2.1",
1909
+ "esrap": "^2.2.4",
1910
+ "is-reference": "^3.0.3",
1911
+ "locate-character": "^3.0.0",
1912
+ "magic-string": "^0.30.11",
1913
+ "zimmerframe": "^1.1.2"
1914
+ },
1915
+ "engines": {
1916
+ "node": ">=18"
1917
+ }
1918
+ },
1919
+ "node_modules/svelte-toolbelt": {
1920
+ "version": "0.7.1",
1921
+ "funding": [
1922
+ "https://github.com/sponsors/huntabyte"
1923
+ ],
1924
+ "dependencies": {
1925
+ "clsx": "^2.1.1",
1926
+ "runed": "^0.23.2",
1927
+ "style-to-object": "^1.0.8"
1928
+ },
1929
+ "engines": {
1930
+ "node": ">=18",
1931
+ "pnpm": ">=8.7.0"
1932
+ },
1933
+ "peerDependencies": {
1934
+ "svelte": "^5.0.0"
1935
+ }
1936
+ },
1937
+ "node_modules/svelte-toolbelt/node_modules/runed": {
1938
+ "version": "0.23.4",
1939
+ "funding": [
1940
+ "https://github.com/sponsors/huntabyte",
1941
+ "https://github.com/sponsors/tglide"
1942
+ ],
1943
+ "dependencies": {
1944
+ "esm-env": "^1.0.0"
1945
+ },
1946
+ "peerDependencies": {
1947
+ "svelte": "^5.7.0"
1948
+ }
1949
+ },
1950
+ "node_modules/tailwindcss": {
1951
+ "version": "4.2.2",
1952
+ "dev": true,
1953
+ "license": "MIT"
1954
+ },
1955
+ "node_modules/tapable": {
1956
+ "version": "2.3.2",
1957
+ "dev": true,
1958
+ "license": "MIT",
1959
+ "engines": {
1960
+ "node": ">=6"
1961
+ },
1962
+ "funding": {
1963
+ "type": "opencollective",
1964
+ "url": "https://opencollective.com/webpack"
1965
+ }
1966
+ },
1967
+ "node_modules/tinyexec": {
1968
+ "version": "1.1.1",
1969
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
1970
+ "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
1971
+ "license": "MIT",
1972
+ "engines": {
1973
+ "node": ">=18"
1974
+ }
1975
+ },
1976
+ "node_modules/tinyglobby": {
1977
+ "version": "0.2.16",
1978
+ "devOptional": true,
1979
+ "license": "MIT",
1980
+ "dependencies": {
1981
+ "fdir": "^6.5.0",
1982
+ "picomatch": "^4.0.4"
1983
+ },
1984
+ "engines": {
1985
+ "node": ">=12.0.0"
1986
+ },
1987
+ "funding": {
1988
+ "url": "https://github.com/sponsors/SuperchupuDev"
1989
+ }
1990
+ },
1991
+ "node_modules/totalist": {
1992
+ "version": "3.0.1",
1993
+ "devOptional": true,
1994
+ "license": "MIT",
1995
+ "engines": {
1996
+ "node": ">=6"
1997
+ }
1998
+ },
1999
+ "node_modules/ts-dedent": {
2000
+ "version": "2.2.0",
2001
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
2002
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
2003
+ "license": "MIT",
2004
+ "engines": {
2005
+ "node": ">=6.10"
2006
+ }
2007
+ },
2008
+ "node_modules/typescript": {
2009
+ "version": "5.9.3",
2010
+ "dev": true,
2011
+ "license": "Apache-2.0",
2012
+ "bin": {
2013
+ "tsc": "bin/tsc",
2014
+ "tsserver": "bin/tsserver"
2015
+ },
2016
+ "engines": {
2017
+ "node": ">=14.17"
2018
+ }
2019
+ },
2020
+ "node_modules/ufo": {
2021
+ "version": "1.6.3",
2022
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
2023
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
2024
+ "license": "MIT"
2025
+ },
2026
+ "node_modules/unist-util-is": {
2027
+ "version": "4.1.0",
2028
+ "dev": true,
2029
+ "license": "MIT",
2030
+ "funding": {
2031
+ "type": "opencollective",
2032
+ "url": "https://opencollective.com/unified"
2033
+ }
2034
+ },
2035
+ "node_modules/unist-util-stringify-position": {
2036
+ "version": "2.0.3",
2037
+ "dev": true,
2038
+ "license": "MIT",
2039
+ "dependencies": {
2040
+ "@types/unist": "^2.0.2"
2041
+ },
2042
+ "funding": {
2043
+ "type": "opencollective",
2044
+ "url": "https://opencollective.com/unified"
2045
+ }
2046
+ },
2047
+ "node_modules/unist-util-visit": {
2048
+ "version": "2.0.3",
2049
+ "dev": true,
2050
+ "license": "MIT",
2051
+ "dependencies": {
2052
+ "@types/unist": "^2.0.0",
2053
+ "unist-util-is": "^4.0.0",
2054
+ "unist-util-visit-parents": "^3.0.0"
2055
+ },
2056
+ "funding": {
2057
+ "type": "opencollective",
2058
+ "url": "https://opencollective.com/unified"
2059
+ }
2060
+ },
2061
+ "node_modules/unist-util-visit-parents": {
2062
+ "version": "3.1.1",
2063
+ "dev": true,
2064
+ "license": "MIT",
2065
+ "dependencies": {
2066
+ "@types/unist": "^2.0.0",
2067
+ "unist-util-is": "^4.0.0"
2068
+ },
2069
+ "funding": {
2070
+ "type": "opencollective",
2071
+ "url": "https://opencollective.com/unified"
2072
+ }
2073
+ },
2074
+ "node_modules/util-deprecate": {
2075
+ "version": "1.0.2",
2076
+ "dev": true,
2077
+ "license": "MIT"
2078
+ },
2079
+ "node_modules/uuid": {
2080
+ "version": "11.1.0",
2081
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
2082
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
2083
+ "funding": [
2084
+ "https://github.com/sponsors/broofa",
2085
+ "https://github.com/sponsors/ctavan"
2086
+ ],
2087
+ "license": "MIT",
2088
+ "bin": {
2089
+ "uuid": "dist/esm/bin/uuid"
2090
+ }
2091
+ },
2092
+ "node_modules/vfile-message": {
2093
+ "version": "2.0.4",
2094
+ "dev": true,
2095
+ "license": "MIT",
2096
+ "dependencies": {
2097
+ "@types/unist": "^2.0.0",
2098
+ "unist-util-stringify-position": "^2.0.0"
2099
+ },
2100
+ "funding": {
2101
+ "type": "opencollective",
2102
+ "url": "https://opencollective.com/unified"
2103
+ }
2104
+ },
2105
+ "node_modules/vite": {
2106
+ "version": "8.0.7",
2107
+ "devOptional": true,
2108
+ "license": "MIT",
2109
+ "dependencies": {
2110
+ "lightningcss": "^1.32.0",
2111
+ "picomatch": "^4.0.4",
2112
+ "postcss": "^8.5.8",
2113
+ "rolldown": "1.0.0-rc.13",
2114
+ "tinyglobby": "^0.2.15"
2115
+ },
2116
+ "bin": {
2117
+ "vite": "bin/vite.js"
2118
+ },
2119
+ "engines": {
2120
+ "node": "^20.19.0 || >=22.12.0"
2121
+ },
2122
+ "funding": {
2123
+ "url": "https://github.com/vitejs/vite?sponsor=1"
2124
+ },
2125
+ "optionalDependencies": {
2126
+ "fsevents": "~2.3.3"
2127
+ },
2128
+ "peerDependencies": {
2129
+ "@types/node": "^20.19.0 || >=22.12.0",
2130
+ "@vitejs/devtools": "^0.1.0",
2131
+ "esbuild": "^0.27.0 || ^0.28.0",
2132
+ "jiti": ">=1.21.0",
2133
+ "less": "^4.0.0",
2134
+ "sass": "^1.70.0",
2135
+ "sass-embedded": "^1.70.0",
2136
+ "stylus": ">=0.54.8",
2137
+ "sugarss": "^5.0.0",
2138
+ "terser": "^5.16.0",
2139
+ "tsx": "^4.8.1",
2140
+ "yaml": "^2.4.2"
2141
+ },
2142
+ "peerDependenciesMeta": {
2143
+ "@types/node": {
2144
+ "optional": true
2145
+ },
2146
+ "@vitejs/devtools": {
2147
+ "optional": true
2148
+ },
2149
+ "esbuild": {
2150
+ "optional": true
2151
+ },
2152
+ "jiti": {
2153
+ "optional": true
2154
+ },
2155
+ "less": {
2156
+ "optional": true
2157
+ },
2158
+ "sass": {
2159
+ "optional": true
2160
+ },
2161
+ "sass-embedded": {
2162
+ "optional": true
2163
+ },
2164
+ "stylus": {
2165
+ "optional": true
2166
+ },
2167
+ "sugarss": {
2168
+ "optional": true
2169
+ },
2170
+ "terser": {
2171
+ "optional": true
2172
+ },
2173
+ "tsx": {
2174
+ "optional": true
2175
+ },
2176
+ "yaml": {
2177
+ "optional": true
2178
+ }
2179
+ }
2180
+ },
2181
+ "node_modules/vitefu": {
2182
+ "version": "1.1.3",
2183
+ "devOptional": true,
2184
+ "license": "MIT",
2185
+ "peer": true,
2186
+ "workspaces": [
2187
+ "tests/deps/*",
2188
+ "tests/projects/*",
2189
+ "tests/projects/workspace/packages/*"
2190
+ ],
2191
+ "peerDependencies": {
2192
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
2193
+ },
2194
+ "peerDependenciesMeta": {
2195
+ "vite": {
2196
+ "optional": true
2197
+ }
2198
+ }
2199
+ },
2200
+ "node_modules/vscode-jsonrpc": {
2201
+ "version": "8.2.0",
2202
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
2203
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
2204
+ "license": "MIT",
2205
+ "engines": {
2206
+ "node": ">=14.0.0"
2207
+ }
2208
+ },
2209
+ "node_modules/vscode-languageserver": {
2210
+ "version": "9.0.1",
2211
+ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
2212
+ "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
2213
+ "license": "MIT",
2214
+ "dependencies": {
2215
+ "vscode-languageserver-protocol": "3.17.5"
2216
+ },
2217
+ "bin": {
2218
+ "installServerIntoExtension": "bin/installServerIntoExtension"
2219
+ }
2220
+ },
2221
+ "node_modules/vscode-languageserver-protocol": {
2222
+ "version": "3.17.5",
2223
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
2224
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
2225
+ "license": "MIT",
2226
+ "dependencies": {
2227
+ "vscode-jsonrpc": "8.2.0",
2228
+ "vscode-languageserver-types": "3.17.5"
2229
+ }
2230
+ },
2231
+ "node_modules/vscode-languageserver-textdocument": {
2232
+ "version": "1.0.12",
2233
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
2234
+ "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
2235
+ "license": "MIT"
2236
+ },
2237
+ "node_modules/vscode-languageserver-types": {
2238
+ "version": "3.17.5",
2239
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
2240
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
2241
+ "license": "MIT"
2242
+ },
2243
+ "node_modules/vscode-uri": {
2244
+ "version": "3.1.0",
2245
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
2246
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
2247
+ "license": "MIT"
2248
+ },
2249
+ "node_modules/zimmerframe": {
2250
+ "version": "1.1.4",
2251
+ "license": "MIT"
2252
+ }
2253
+ }
2254
+}
docs/package.json
new
+27
@@ -0,0 +1,27 @@
1
+{
2
+ "name": "portal-docs",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "vite dev",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "devDependencies": {
11
+ "@sveltejs/adapter-static": "^3.0.0",
12
+ "@sveltejs/kit": "^2.0.0",
13
+ "@tailwindcss/typography": "^0.5.0",
14
+ "@tailwindcss/vite": "^4.0.0",
15
+ "mdsvex": "^0.12.0",
16
+ "svelte": "^5.0.0",
17
+ "tailwindcss": "^4.0.0",
18
+ "typescript": "^5.0.0",
19
+ "vite": "^8.0.0"
20
+ },
21
+ "type": "module",
22
+ "dependencies": {
23
+ "mermaid": "^11.14.0",
24
+ "mode-watcher": "^1.1.0",
25
+ "runed": "^0.37.1"
26
+ }
27
+}
docs/src/app.d.ts
new
+13
@@ -0,0 +1,13 @@
1
+// See https://svelte.dev/docs/kit/types#app.d.ts
2
+// for information about these interfaces
3
+declare global {
4
+ namespace App {
5
+ // interface Error {}
6
+ // interface Locals {}
7
+ // interface PageData {}
8
+ // interface PageState {}
9
+ // interface Platform {}
10
+ }
11
+}
12
+
13
+export {};
docs/src/app.html
new
+16
@@ -0,0 +1,16 @@
1
+<!doctype html>
2
+<html lang="en" class="scroll-smooth">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <link rel="icon" href="%sveltekit.assets%/favicon.ico" sizes="48x48" />
7
+ <link rel="icon" href="%sveltekit.assets%/favicon.svg" type="image/svg+xml" />
8
+ <link rel="icon" href="%sveltekit.assets%/favicon-96x96.png" type="image/png" sizes="96x96" />
9
+ <link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
10
+ <title>Portal Documentation</title>
11
+ %sveltekit.head%
12
+ </head>
13
+ <body data-sveltekit-preload-data="hover">
14
+ <div style="display: contents">%sveltekit.body%</div>
15
+ </body>
16
+</html>
docs/src/lib/nav.ts
new
+40
@@ -0,0 +1,40 @@
1
+export interface NavItem {
2
+ title: string;
3
+ href: string;
4
+}
5
+
6
+export interface NavSection {
7
+ title: string;
8
+ badge?: string;
9
+ items: NavItem[];
10
+}
11
+
12
+export const navigation: NavSection[] = [
13
+ {
14
+ title: 'Getting Started',
15
+ items: [
16
+ { title: 'Quick Start', href: '/getting-started' },
17
+ { title: 'Concepts', href: '/concepts' },
18
+ { title: 'Self-Hosting', href: '/self-hosting' }
19
+ ]
20
+ },
21
+ {
22
+ title: 'Guides',
23
+ items: [{ title: 'TCP/UDP Tunneling', href: '/tcp-udp-tunneling' }]
24
+ },
25
+ {
26
+ title: 'Reference',
27
+ items: [
28
+ { title: 'CLI Reference', href: '/cli-reference' },
29
+ { title: 'API Reference', href: '/api-reference' },
30
+ { title: 'Configuration', href: '/configuration' }
31
+ ]
32
+ },
33
+ {
34
+ title: 'Advanced',
35
+ items: [
36
+ { title: 'Architecture', href: '/architecture' },
37
+ { title: 'Deployment', href: '/deployment' }
38
+ ]
39
+ }
40
+];
docs/src/mdsvex.d.ts
new
+8
@@ -0,0 +1,8 @@
1
+declare module '*.md' {
2
+ import type { Component } from 'svelte';
3
+
4
+ const component: Component;
5
+ export default component;
6
+
7
+ export const metadata: Record<string, unknown>;
8
+}
docs/src/routes/+error.svelte
new
+17
@@ -0,0 +1,17 @@
1
+<script lang="ts">
2
+ import { page } from '$app/stores';
3
+ import { base } from '$app/paths';
4
+</script>
5
+
6
+<div class="py-16 text-center">
7
+ <h1 class="text-4xl font-bold text-gray-900 dark:text-white">{$page.status}</h1>
8
+ <p class="mt-4 text-lg text-gray-600 dark:text-gray-400">
9
+ {$page.error?.message || 'Page not found'}
10
+ </p>
11
+ <a
12
+ href="{base}/"
13
+ class="mt-8 inline-block rounded-lg bg-blue-600 px-6 py-3 text-sm font-medium text-white hover:bg-blue-700"
14
+ >
15
+ Back to Home
16
+ </a>
17
+</div>
docs/src/routes/+layout.ts
new
+2
@@ -0,0 +1,2 @@
1
+export const prerender = true;
2
+export const trailingSlash = 'always';
docs/src/routes/api-reference/+page.md
new
+206
@@ -0,0 +1,206 @@
1
+---
2
+title: API Reference
3
+description: Complete API reference for Portal relay server endpoints.
4
+---
5
+
6
+<script>
7
+import Mermaid from '$lib/components/Mermaid.svelte'
8
+</script>
9
+
10
+# API Reference
11
+
12
+This page provides a complete reference for the Portal relay server HTTP API. All endpoints are served over HTTPS with keyless TLS.
13
+
14
+## Response Envelope
15
+
16
+Every API response uses a consistent JSON envelope:
17
+
18
+```json
19
+{
20
+ "ok": true,
21
+ "data": { ... },
22
+ "error": null
23
+}
24
+```
25
+
26
+| Field | Type | Description |
27
+|-------|------|-------------|
28
+| `ok` | `boolean` | `true` if the request succeeded |
29
+| `data` | `T` | Response payload (omitted on error) |
30
+| `error` | `object \| null` | Error details (omitted on success) |
31
+
32
+Error responses include a structured error object:
33
+
34
+```json
35
+{
36
+ "ok": false,
37
+ "error": {
38
+ "code": "unauthorized",
39
+ "message": "unauthorized"
40
+ }
41
+}
42
+```
43
+
44
+## Authentication
45
+
46
+Portal uses two separate authentication mechanisms depending on the caller.
47
+
48
+### SDK Authentication (SIWE Challenge/Response)
49
+
50
+SDK clients authenticate using Sign-In with Ethereum (SIWE):
51
+
52
+1. POST a challenge request to `/sdk/register/challenge` with your identity
53
+2. Sign the returned SIWE message with your Ethereum private key
54
+3. POST the signed message to `/sdk/register` to receive a JWT access token
55
+4. Include the access token in subsequent requests via the `X-Portal-Access-Token` header or in the JSON request body
56
+
57
+### Admin Authentication (Secret Key)
58
+
59
+Admin clients authenticate using a shared secret key:
60
+
61
+1. POST to `/admin/login` with `{ "key": "<secret>" }`
62
+2. The server sets a `portal_admin` session cookie (HttpOnly, Secure, SameSite=Strict)
63
+3. Include the cookie in subsequent admin requests
64
+4. Sessions expire after 24 hours
65
+
66
+## Endpoint Summary
67
+
68
+### SDK Endpoints
69
+
70
+| Method | Path | Description | Auth |
71
+|--------|------|-------------|------|
72
+| `GET` | [`/sdk/domain`](/api-reference/sdk#get-sdkdomain) | Get relay domain and version info | None |
73
+| `POST` | [`/sdk/register/challenge`](/api-reference/sdk#post-sdkregisterchallenge) | Request a SIWE challenge for registration | None |
74
+| `POST` | [`/sdk/register`](/api-reference/sdk#post-sdkregister) | Complete registration with signed challenge | None |
75
+| `POST` | [`/sdk/renew`](/api-reference/sdk#post-sdkrenew) | Renew an existing lease TTL | Access Token |
76
+| `POST` | [`/sdk/unregister`](/api-reference/sdk#post-sdkunregister) | Remove an active lease | Access Token |
77
+| `GET` | [`/sdk/connect`](/api-reference/sdk#get-sdkconnect) | Establish reverse tunnel connection | Access Token |
78
+
79
+### Admin Endpoints
80
+
81
+| Method | Path | Description | Auth |
82
+|--------|------|-------------|------|
83
+| `POST` | [`/admin/login`](/api-reference/admin#post-adminlogin) | Authenticate with secret key | None |
84
+| `POST` | [`/admin/logout`](/api-reference/admin#post-adminlogout) | End admin session | Session Cookie |
85
+| `GET` | [`/admin/auth/status`](/api-reference/admin#get-adminauthstatus) | Check authentication status | None |
86
+| `GET` | [`/admin/snapshot`](/api-reference/admin#get-adminsnapshot) | Get full relay state snapshot | Session Cookie |
87
+| `POST` | [`/admin/settings/landing-page`](/api-reference/admin#post-adminsettingslanding-page) | Toggle landing page | Session Cookie |
88
+| `POST` | [`/admin/settings/udp`](/api-reference/admin#post-adminsettingsudp) | Configure UDP settings | Session Cookie |
89
+| `POST` | [`/admin/settings/tcp-port`](/api-reference/admin#post-adminsettingstcp-port) | Configure TCP port settings | Session Cookie |
90
+| `POST` | [`/admin/settings/approval-mode`](/api-reference/admin#post-adminsettingsapproval-mode) | Set approval mode | Session Cookie |
91
+| `POST` | [`/admin/leases/{name}/{addr}/ban`](/api-reference/admin#lease-management) | Ban a lease identity | Session Cookie |
92
+| `DELETE` | [`/admin/leases/{name}/{addr}/ban`](/api-reference/admin#lease-management) | Unban a lease identity | Session Cookie |
93
+| `POST` | [`/admin/leases/{name}/{addr}/bps`](/api-reference/admin#lease-management) | Set bandwidth limit for a lease | Session Cookie |
94
+| `DELETE` | [`/admin/leases/{name}/{addr}/bps`](/api-reference/admin#lease-management) | Remove bandwidth limit | Session Cookie |
95
+| `POST` | [`/admin/leases/{name}/{addr}/approve`](/api-reference/admin#lease-management) | Approve a lease | Session Cookie |
96
+| `DELETE` | [`/admin/leases/{name}/{addr}/approve`](/api-reference/admin#lease-management) | Revoke lease approval | Session Cookie |
97
+| `POST` | [`/admin/leases/{name}/{addr}/deny`](/api-reference/admin#lease-management) | Deny a lease | Session Cookie |
98
+| `DELETE` | [`/admin/leases/{name}/{addr}/deny`](/api-reference/admin#lease-management) | Remove lease denial | Session Cookie |
99
+| `POST` | [`/admin/ips/{ip}/ban`](/api-reference/admin#ip-management) | Ban an IP address | Session Cookie |
100
+| `DELETE` | [`/admin/ips/{ip}/ban`](/api-reference/admin#ip-management) | Unban an IP address | Session Cookie |
101
+
102
+### System Endpoints
103
+
104
+| Method | Path | Description | Auth |
105
+|--------|------|-------------|------|
106
+| `GET` | `/healthz` | Health check | None |
107
+| `GET` | `/discovery` | Relay discovery | None |
108
+| `GET` | `/v1/sign` | Keyless TLS signing | None |
109
+| `GET` | `/thumbnail/{hostname}` | Cached thumbnail screenshot | None |
110
+| `GET` | `/tunnel/status` | Tunnel connection status | Access Token |
111
+
112
+## System Endpoints
113
+
114
+These endpoints are small enough to document inline.
115
+
116
+### `GET /healthz`
117
+
118
+Returns relay health status.
119
+
120
+**Response:**
121
+
122
+```json
123
+{
124
+ "ok": true,
125
+ "data": {
126
+ "status": "ok"
127
+ }
128
+}
129
+```
130
+
131
+### `GET /discovery`
132
+
133
+Returns relay discovery information including the relay's own descriptor and any known peer relays. Only available when discovery is enabled in the server configuration.
134
+
135
+**Response fields:**
136
+
137
+| Field | Type | Description |
138
+|-------|------|-------------|
139
+| `protocol_version` | `string` | Protocol version identifier |
140
+| `generated_at` | `string` | ISO 8601 timestamp |
141
+| `self` | `RelayDescriptor` | This relay's descriptor |
142
+| `relays` | `RelayDescriptor[]` | Known peer relay descriptors |
143
+
144
+**Example:**
145
+
146
+```bash
147
+curl https://relay.example.com/discovery
148
+```
149
+
150
+### `GET /v1/sign`
151
+
152
+Keyless TLS signing endpoint. Used by the relay's keyless TLS infrastructure. Only available when the API server is configured with a TLS private key.
153
+
154
+Returns `404 Not Found` if signing is not configured.
155
+
156
+### `GET /thumbnail/{hostname}`
157
+
158
+Returns a cached thumbnail screenshot for a registered tunnel hostname.
159
+
160
+**Response headers:**
161
+
162
+| Header | Value |
163
+|--------|-------|
164
+| `Content-Type` | Image content type (e.g. `image/png`) |
165
+| `Cache-Control` | `public, max-age=300` |
166
+
167
+Returns `404 Not Found` if the hostname is not registered or no thumbnail is available.
168
+
169
+**Example:**
170
+
171
+```bash
172
+curl https://relay.example.com/thumbnail/myapp.relay.example.com
173
+```
174
+
175
+## Error Codes
176
+
177
+All error codes that may appear in the `error.code` field:
178
+
179
+| Code | Description |
180
+|------|-------------|
181
+| `auth_disabled` | Admin authentication is not configured |
182
+| `feature_unavailable` | Requested feature is not available |
183
+| `hijack_failed` | HTTP connection hijack failed |
184
+| `hijack_unsupported` | HTTP connection hijack not supported |
185
+| `hostname_conflict` | Hostname already in use by another lease |
186
+| `http11_only` | Endpoint requires HTTP/1.1 |
187
+| `invalid_address` | Invalid Ethereum address |
188
+| `invalid_ip` | Invalid IP address format |
189
+| `invalid_json` | Malformed JSON request body |
190
+| `invalid_key` | Invalid admin secret key |
191
+| `invalid_mode` | Invalid approval mode value |
192
+| `invalid_request` | General request validation failure |
193
+| `internal` | Internal server error |
194
+| `ip_banned` | Source IP is banned |
195
+| `lease_not_found` | No lease found for the given identity |
196
+| `lease_rejected` | Lease is not approved for routing |
197
+| `method_not_allowed` | HTTP method not allowed for this endpoint |
198
+| `session_create_failed` | Failed to create admin session |
199
+| `unauthorized` | Authentication required or token invalid |
200
+| `udp_port_exhausted` | No UDP ports available |
201
+| `udp_disabled` | UDP transport is disabled |
202
+| `udp_capacity_exceeded` | UDP lease capacity reached |
203
+| `tcp_port_exhausted` | No TCP ports available |
204
+| `tcp_port_disabled` | TCP port transport is disabled |
205
+| `tcp_port_capacity_exceeded` | TCP port lease capacity reached |
206
+| `transport_mismatch` | Transport type mismatch |
docs/src/routes/api-reference/admin/+page.md
new
+547
@@ -0,0 +1,547 @@
1
+---
2
+title: Admin API
3
+description: Portal relay admin endpoints for managing leases, settings, and access control.
4
+---
5
+
6
+<script>
7
+import Mermaid from '$lib/components/Mermaid.svelte'
8
+
9
+const adminWorkflowDiagram = `sequenceDiagram
10
+ participant Admin
11
+ participant Relay as Portal Relay
12
+ Admin->>Relay: POST /admin/login
13
+ Note right of Admin: secret key in body
14
+ Relay->>Admin: Set-Cookie session token
15
+ Admin->>Relay: GET /admin/snapshot
16
+ Relay->>Admin: Full relay state
17
+ Note left of Relay: leases, settings, bans
18
+ alt Manage Leases
19
+ Admin->>Relay: POST /admin/leases/.../ban
20
+ Relay->>Admin: OK
21
+ end
22
+ alt Configure Settings
23
+ Admin->>Relay: POST /admin/settings/approval-mode
24
+ Relay->>Admin: Updated settings
25
+ end
26
+ Admin->>Relay: POST /admin/logout
27
+ Relay->>Admin: Session cleared`
28
+</script>
29
+
30
+# Admin API
31
+
32
+These endpoints allow relay operators to manage leases, configure settings, and control access. All endpoints (except authentication) require a valid admin session cookie.
33
+
34
+## Admin Workflow
35
+
36
+<Mermaid code={adminWorkflowDiagram} />
37
+
38
+---
39
+
40
+## Authentication
41
+
42
+### `POST /admin/login`
43
+
44
+Authenticate with the admin secret key. On success, sets a session cookie used for all subsequent admin requests.
45
+
46
+**Auth:** None
47
+
48
+**Request body:**
49
+
50
+| Field | Type | Required | Description |
51
+|-------|------|----------|-------------|
52
+| `key` | `string` | Yes | Admin secret key |
53
+
54
+**Response fields:**
55
+
56
+| Field | Type | Description |
57
+|-------|------|-------------|
58
+| `success` | `bool` | `true` on successful login |
59
+
60
+**Response cookies:**
61
+
62
+| Cookie | Value | Attributes |
63
+|--------|-------|------------|
64
+| `portal_admin` | Session token | `Path=/admin; HttpOnly; Secure; SameSite=Strict; MaxAge=86400` |
65
+
66
+**Error codes:**
67
+
68
+| Code | Status | Description |
69
+|------|--------|-------------|
70
+| `auth_disabled` | 503 | Admin authentication is not configured |
71
+| `invalid_key` | 401 | Incorrect secret key |
72
+
73
+**Example:**
74
+
75
+```bash
76
+curl -X POST https://relay.example.com/admin/login \
77
+ -H "Content-Type: application/json" \
78
+ -c cookies.txt \
79
+ -d '{ "key": "my-secret-key" }'
80
+```
81
+
82
+**Response:**
83
+
84
+```json
85
+{
86
+ "ok": true,
87
+ "data": {
88
+ "success": true
89
+ }
90
+}
91
+```
92
+
93
+---
94
+
95
+### `POST /admin/logout`
96
+
97
+End the current admin session and clear the session cookie.
98
+
99
+**Auth:** Session Cookie
100
+
101
+**Request body:** None
102
+
103
+**Response:** Empty data object.
104
+
105
+**Example:**
106
+
107
+```bash
108
+curl -X POST https://relay.example.com/admin/logout \
109
+ -b cookies.txt
110
+```
111
+
112
+---
113
+
114
+### `GET /admin/auth/status`
115
+
116
+Check the current authentication status. Can be called without a session to determine whether admin auth is enabled.
117
+
118
+**Auth:** None (returns status regardless)
119
+
120
+**Response fields:**
121
+
122
+| Field | Type | Description |
123
+|-------|------|-------------|
124
+| `authenticated` | `bool` | `true` if the request has a valid session |
125
+| `auth_enabled` | `bool` | `true` if admin auth is configured |
126
+
127
+**Example:**
128
+
129
+```bash
130
+curl https://relay.example.com/admin/auth/status \
131
+ -b cookies.txt
132
+```
133
+
134
+**Response:**
135
+
136
+```json
137
+{
138
+ "ok": true,
139
+ "data": {
140
+ "authenticated": true,
141
+ "auth_enabled": true
142
+ }
143
+}
144
+```
145
+
146
+---
147
+
148
+## State
149
+
150
+### `GET /admin/snapshot`
151
+
152
+Get a full snapshot of the relay's current state including all active leases, approval mode, and transport settings.
153
+
154
+**Auth:** Session Cookie
155
+
156
+**Response fields:**
157
+
158
+| Field | Type | Description |
159
+|-------|------|-------------|
160
+| `approval_mode` | `string` | Current mode: `"auto"` or `"manual"` |
161
+| `landing_page_enabled` | `bool` | Whether the landing page is active |
162
+| `leases` | `AdminLease[]` | All active leases (see below) |
163
+| `udp` | `object` | UDP settings |
164
+| `udp.enabled` | `bool` | Whether UDP transport is enabled |
165
+| `udp.max_leases` | `int` | Maximum concurrent UDP leases (0 = unlimited) |
166
+| `tcp_port` | `object` | TCP port settings |
167
+| `tcp_port.enabled` | `bool` | Whether TCP port transport is enabled |
168
+| `tcp_port.max_leases` | `int` | Maximum concurrent TCP port leases (0 = unlimited) |
169
+
170
+**AdminLease fields:**
171
+
172
+| Field | Type | Description |
173
+|-------|------|-------------|
174
+| `identity_key` | `string` | Unique identity key |
175
+| `address` | `string` | Ethereum address |
176
+| `name` | `string` | Lease name |
177
+| `hostname` | `string` | Assigned hostname |
178
+| `expires_at` | `string` | ISO 8601 lease expiration |
179
+| `first_seen_at` | `string` | ISO 8601 first registration time |
180
+| `last_seen_at` | `string` | ISO 8601 last activity time |
181
+| `client_ip` | `string` | Client IP address |
182
+| `reported_ip` | `string` | Client-reported public IP |
183
+| `udp_addr` | `string` | UDP transport address |
184
+| `tcp_addr` | `string` | TCP transport address |
185
+| `metadata` | `object` | Lease metadata (description, tags, thumbnail) |
186
+| `ready` | `int` | Number of ready reverse connections |
187
+| `bps` | `int` | Bandwidth limit in bytes per second (0 = unlimited) |
188
+
189
+**Example:**
190
+
191
+```bash
192
+curl https://relay.example.com/admin/snapshot \
193
+ -b cookies.txt
194
+```
195
+
196
+**Response:**
197
+
198
+```json
199
+{
200
+ "ok": true,
201
+ "data": {
202
+ "approval_mode": "auto",
203
+ "landing_page_enabled": true,
204
+ "leases": [
205
+ {
206
+ "identity_key": "my-app:0x1234...5678",
207
+ "address": "0x1234...5678",
208
+ "name": "my-app",
209
+ "hostname": "my-app.relay.example.com",
210
+ "expires_at": "2025-01-01T00:01:00Z",
211
+ "first_seen_at": "2025-01-01T00:00:00Z",
212
+ "last_seen_at": "2025-01-01T00:00:30Z",
213
+ "client_ip": "203.0.113.1",
214
+ "ready": 2,
215
+ "bps": 0
216
+ }
217
+ ],
218
+ "udp": {
219
+ "enabled": true,
220
+ "max_leases": 10
221
+ },
222
+ "tcp_port": {
223
+ "enabled": false,
224
+ "max_leases": 0
225
+ }
226
+ }
227
+}
228
+```
229
+
230
+---
231
+
232
+## Settings
233
+
234
+### `POST /admin/settings/landing-page`
235
+
236
+Enable or disable the relay landing page.
237
+
238
+**Auth:** Session Cookie
239
+
240
+**Request body:**
241
+
242
+| Field | Type | Required | Description |
243
+|-------|------|----------|-------------|
244
+| `enabled` | `bool` | Yes | `true` to enable, `false` to disable |
245
+
246
+**Response fields:**
247
+
248
+| Field | Type | Description |
249
+|-------|------|-------------|
250
+| `enabled` | `bool` | Current landing page state |
251
+
252
+**Example:**
253
+
254
+```bash
255
+curl -X POST https://relay.example.com/admin/settings/landing-page \
256
+ -H "Content-Type: application/json" \
257
+ -b cookies.txt \
258
+ -d '{ "enabled": true }'
259
+```
260
+
261
+---
262
+
263
+### `POST /admin/settings/udp`
264
+
265
+Configure UDP (QUIC) transport settings.
266
+
267
+**Auth:** Session Cookie
268
+
269
+**Request body:**
270
+
271
+| Field | Type | Required | Description |
272
+|-------|------|----------|-------------|
273
+| `enabled` | `bool` | Yes | Enable or disable UDP transport |
274
+| `max_leases` | `int` | Yes | Maximum concurrent UDP leases (0 = unlimited) |
275
+
276
+**Response fields:**
277
+
278
+| Field | Type | Description |
279
+|-------|------|-------------|
280
+| `enabled` | `bool` | Current UDP enabled state |
281
+| `max_leases` | `int` | Current max leases value |
282
+
283
+**Error codes:**
284
+
285
+| Code | Status | Description |
286
+|------|--------|-------------|
287
+| `invalid_request` | 400 | `max_leases` must be non-negative |
288
+
289
+**Example:**
290
+
291
+```bash
292
+curl -X POST https://relay.example.com/admin/settings/udp \
293
+ -H "Content-Type: application/json" \
294
+ -b cookies.txt \
295
+ -d '{ "enabled": true, "max_leases": 10 }'
296
+```
297
+
298
+---
299
+
300
+### `POST /admin/settings/tcp-port`
301
+
302
+Configure dedicated TCP port transport settings.
303
+
304
+**Auth:** Session Cookie
305
+
306
+**Request body:**
307
+
308
+| Field | Type | Required | Description |
309
+|-------|------|----------|-------------|
310
+| `enabled` | `bool` | Yes | Enable or disable TCP port transport |
311
+| `max_leases` | `int` | Yes | Maximum concurrent TCP port leases (0 = unlimited) |
312
+
313
+**Response fields:**
314
+
315
+| Field | Type | Description |
316
+|-------|------|-------------|
317
+| `enabled` | `bool` | Current TCP port enabled state |
318
+| `max_leases` | `int` | Current max leases value |
319
+
320
+**Error codes:**
321
+
322
+| Code | Status | Description |
323
+|------|--------|-------------|
324
+| `invalid_request` | 400 | `max_leases` must be non-negative |
325
+
326
+**Example:**
327
+
328
+```bash
329
+curl -X POST https://relay.example.com/admin/settings/tcp-port \
330
+ -H "Content-Type: application/json" \
331
+ -b cookies.txt \
332
+ -d '{ "enabled": true, "max_leases": 5 }'
333
+```
334
+
335
+---
336
+
337
+### `POST /admin/settings/approval-mode`
338
+
339
+Set the lease approval mode. In `auto` mode, all leases are automatically approved. In `manual` mode, leases must be explicitly approved before they can route traffic.
340
+
341
+**Auth:** Session Cookie
342
+
343
+**Request body:**
344
+
345
+| Field | Type | Required | Description |
346
+|-------|------|----------|-------------|
347
+| `mode` | `string` | Yes | `"auto"` or `"manual"` |
348
+
349
+**Response fields:**
350
+
351
+| Field | Type | Description |
352
+|-------|------|-------------|
353
+| `approval_mode` | `string` | Current approval mode |
354
+
355
+**Error codes:**
356
+
357
+| Code | Status | Description |
358
+|------|--------|-------------|
359
+| `invalid_mode` | 400 | Mode must be `"auto"` or `"manual"` |
360
+
361
+**Example:**
362
+
363
+```bash
364
+curl -X POST https://relay.example.com/admin/settings/approval-mode \
365
+ -H "Content-Type: application/json" \
366
+ -b cookies.txt \
367
+ -d '{ "mode": "manual" }'
368
+```
369
+
370
+**Response:**
371
+
372
+```json
373
+{
374
+ "ok": true,
375
+ "data": {
376
+ "approval_mode": "manual"
377
+ }
378
+}
379
+```
380
+
381
+---
382
+
383
+## Lease Management
384
+
385
+Lease management endpoints use base64url-encoded identity components in the URL path:
386
+
387
+```
388
+/admin/leases/{name_b64}/{addr_b64}/{action}
389
+```
390
+
391
+Where `{name_b64}` is the base64url-encoded lease name and `{addr_b64}` is the base64url-encoded Ethereum address.
392
+
393
+All lease management endpoints return an empty data object on success. All changes are persisted to the admin state file.
394
+
395
+### `POST|DELETE /admin/leases/{name}/{addr}/ban`
396
+
397
+Ban or unban a lease identity. Banned identities cannot register new leases or renew existing ones.
398
+
399
+**Auth:** Session Cookie
400
+
401
+| Method | Description |
402
+|--------|-------------|
403
+| `POST` | Ban the identity |
404
+| `DELETE` | Remove the ban |
405
+
406
+**Example:**
407
+
408
+```bash
409
+# Ban an identity
410
+curl -X POST https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/ban \
411
+ -b cookies.txt
412
+
413
+# Unban an identity
414
+curl -X DELETE https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/ban \
415
+ -b cookies.txt
416
+```
417
+
418
+---
419
+
420
+### `POST|DELETE /admin/leases/{name}/{addr}/bps`
421
+
422
+Set or remove a bandwidth limit (bytes per second) for a specific lease identity.
423
+
424
+**Auth:** Session Cookie
425
+
426
+| Method | Description |
427
+|--------|-------------|
428
+| `POST` | Set bandwidth limit |
429
+| `DELETE` | Remove bandwidth limit |
430
+
431
+**Request body (POST only):**
432
+
433
+| Field | Type | Required | Description |
434
+|-------|------|----------|-------------|
435
+| `bps` | `int` | Yes | Bandwidth limit in bytes per second (must be > 0) |
436
+
437
+**Error codes:**
438
+
439
+| Code | Status | Description |
440
+|------|--------|-------------|
441
+| `invalid_request` | 400 | `bps` must be greater than zero |
442
+
443
+**Example:**
444
+
445
+```bash
446
+# Set 1 MB/s bandwidth limit
447
+curl -X POST https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/bps \
448
+ -H "Content-Type: application/json" \
449
+ -b cookies.txt \
450
+ -d '{ "bps": 1048576 }'
451
+
452
+# Remove bandwidth limit
453
+curl -X DELETE https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/bps \
454
+ -b cookies.txt
455
+```
456
+
457
+---
458
+
459
+### `POST|DELETE /admin/leases/{name}/{addr}/approve`
460
+
461
+Approve or revoke approval for a lease identity. Only relevant when approval mode is `manual`.
462
+
463
+**Auth:** Session Cookie
464
+
465
+| Method | Description |
466
+|--------|-------------|
467
+| `POST` | Approve the identity (also removes any deny) |
468
+| `DELETE` | Revoke approval |
469
+
470
+**Example:**
471
+
472
+```bash
473
+# Approve an identity
474
+curl -X POST https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/approve \
475
+ -b cookies.txt
476
+
477
+# Revoke approval
478
+curl -X DELETE https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/approve \
479
+ -b cookies.txt
480
+```
481
+
482
+---
483
+
484
+### `POST|DELETE /admin/leases/{name}/{addr}/deny`
485
+
486
+Deny or remove denial for a lease identity. Denied identities are blocked from routing even in `auto` mode.
487
+
488
+**Auth:** Session Cookie
489
+
490
+| Method | Description |
491
+|--------|-------------|
492
+| `POST` | Deny the identity |
493
+| `DELETE` | Remove the denial |
494
+
495
+**Example:**
496
+
497
+```bash
498
+# Deny an identity
499
+curl -X POST https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/deny \
500
+ -b cookies.txt
501
+
502
+# Remove denial
503
+curl -X DELETE https://relay.example.com/admin/leases/bXktYXBw/MHgxMjM0/deny \
504
+ -b cookies.txt
505
+```
506
+
507
+---
508
+
509
+## IP Management
510
+
511
+### `POST|DELETE /admin/ips/{ip}/ban`
512
+
513
+Ban or unban an IP address. Banned IPs are rejected at the SDK registration and renewal endpoints.
514
+
515
+**Auth:** Session Cookie
516
+
517
+| Method | Description |
518
+|--------|-------------|
519
+| `POST` | Ban the IP address |
520
+| `DELETE` | Unban the IP address |
521
+
522
+**Error codes:**
523
+
524
+| Code | Status | Description |
525
+|------|--------|-------------|
526
+| `invalid_ip` | 400 | Invalid IP address format |
527
+
528
+**Example:**
529
+
530
+```bash
531
+# Ban an IP
532
+curl -X POST https://relay.example.com/admin/ips/203.0.113.50/ban \
533
+ -b cookies.txt
534
+
535
+# Unban an IP
536
+curl -X DELETE https://relay.example.com/admin/ips/203.0.113.50/ban \
537
+ -b cookies.txt
538
+```
539
+
540
+**Response:**
541
+
542
+```json
543
+{
544
+ "ok": true,
545
+ "data": {}
546
+}
547
+```
docs/src/routes/api-reference/sdk/+page.md
new
+369
@@ -0,0 +1,369 @@
1
+---
2
+title: SDK API
3
+description: Portal SDK endpoints for tunnel registration and connection.
4
+---
5
+
6
+<script>
7
+import Mermaid from '$lib/components/Mermaid.svelte'
8
+
9
+const registrationDiagram = `sequenceDiagram
10
+ participant Client as SDK Client
11
+ participant Relay as Portal Relay
12
+
13
+ Client->>Relay: POST /sdk/register/challenge
14
+ Note right of Client: Send identity + metadata
15
+ Relay->>Client: challenge_id + siwe_message
16
+
17
+ Client->>Client: Sign SIWE message with private key
18
+
19
+ Client->>Relay: POST /sdk/register
20
+ Note right of Client: challenge_id + signed message
21
+ Relay->>Client: access_token + lease info
22
+ Note left of Relay: hostname, udp_addr, tcp_addr`
23
+
24
+const reverseConnectDiagram = `sequenceDiagram
25
+ participant SDK as SDK Client
26
+ participant Relay as Portal Relay
27
+ participant Browser as End User
28
+
29
+ SDK->>Relay: GET /sdk/connect
30
+ Note right of SDK: X-Portal-Access-Token header
31
+ Relay->>SDK: HTTP/1.1 200 OK (hijacked)
32
+ Note over SDK,Relay: Connection upgraded to raw TCP
33
+
34
+ Browser->>Relay: TLS ClientHello (SNI: app.relay.example.com)
35
+ Relay->>Relay: Match SNI to lease
36
+ Relay->>SDK: Forward traffic over hijacked conn
37
+ SDK->>Relay: Response traffic
38
+ Relay->>Browser: Forward response
39
+ Note over SDK,Browser: Bidirectional bridge established`
40
+</script>
41
+
42
+# SDK API
43
+
44
+These endpoints are used by the Portal SDK to register tunnels, manage leases, and establish reverse connections to the relay server.
45
+
46
+## Registration Flow
47
+
48
+<Mermaid code={registrationDiagram} />
49
+
50
+## Reverse Connect Flow
51
+
52
+<Mermaid code={reverseConnectDiagram} />
53
+
54
+---
55
+
56
+### `GET /sdk/domain`
57
+
58
+Get relay domain and protocol version information. Used by the SDK to verify relay compatibility before registration.
59
+
60
+**Auth:** None
61
+
62
+**Response fields:**
63
+
64
+| Field | Type | Description |
65
+|-------|------|-------------|
66
+| `protocol_version` | `string` | Protocol version (must match SDK version) |
67
+| `release_version` | `string` | Relay software release version |
68
+
69
+**Example:**
70
+
71
+```bash
72
+curl https://relay.example.com/sdk/domain
73
+```
74
+
75
+**Response:**
76
+
77
+```json
78
+{
79
+ "ok": true,
80
+ "data": {
81
+ "protocol_version": "2.0",
82
+ "release_version": "v2.1.0"
83
+ }
84
+}
85
+```
86
+
87
+---
88
+
89
+### `POST /sdk/register/challenge`
90
+
91
+Request a SIWE (Sign-In with Ethereum) challenge message for tunnel registration. This is the first step of the two-phase registration flow.
92
+
93
+**Auth:** None
94
+
95
+**Request body:**
96
+
97
+| Field | Type | Required | Description |
98
+|-------|------|----------|-------------|
99
+| `identity` | `object` | Yes | Identity object (see below) |
100
+| `identity.name` | `string` | Yes | Lease name (used as subdomain) |
101
+| `identity.address` | `string` | Yes | Ethereum address (hex, `0x`-prefixed) |
102
+| `metadata` | `object` | No | Lease metadata |
103
+| `metadata.description` | `string` | No | Human-readable description |
104
+| `metadata.tags` | `string[]` | No | Tags for categorization |
105
+| `metadata.thumbnail` | `string` | No | Base64-encoded thumbnail image |
106
+| `ttl` | `int` | No | Lease TTL in seconds (default: server-configured) |
107
+| `udp_enabled` | `bool` | No | Request UDP (QUIC) transport |
108
+| `tcp_enabled` | `bool` | No | Request dedicated TCP port |
109
+
110
+**Response fields:**
111
+
112
+| Field | Type | Description |
113
+|-------|------|-------------|
114
+| `challenge_id` | `string` | Unique challenge identifier |
115
+| `expires_at` | `string` | ISO 8601 challenge expiration |
116
+| `siwe_message` | `string` | SIWE message to sign |
117
+
118
+**Error codes:**
119
+
120
+| Code | Status | Description |
121
+|------|--------|-------------|
122
+| `ip_banned` | 403 | Source IP is banned |
123
+| `feature_unavailable` | 503 | UDP or TCP transport not available |
124
+
125
+**Example:**
126
+
127
+```bash
128
+curl -X POST https://relay.example.com/sdk/register/challenge \
129
+ -H "Content-Type: application/json" \
130
+ -d '{
131
+ "identity": {
132
+ "name": "my-app",
133
+ "address": "0x1234567890abcdef1234567890abcdef12345678"
134
+ },
135
+ "metadata": {
136
+ "description": "My web application"
137
+ },
138
+ "ttl": 60
139
+ }'
140
+```
141
+
142
+**Response:**
143
+
144
+```json
145
+{
146
+ "ok": true,
147
+ "data": {
148
+ "challenge_id": "abc123",
149
+ "expires_at": "2025-01-01T00:05:00Z",
150
+ "siwe_message": "relay.example.com wants you to sign in..."
151
+ }
152
+}
153
+```
154
+
155
+---
156
+
157
+### `POST /sdk/register`
158
+
159
+Complete tunnel registration by submitting the signed SIWE challenge. Returns an access token and lease information including the assigned hostname.
160
+
161
+**Auth:** None (authenticated by SIWE signature)
162
+
163
+**Request body:**
164
+
165
+| Field | Type | Required | Description |
166
+|-------|------|----------|-------------|
167
+| `challenge_id` | `string` | Yes | Challenge ID from `/sdk/register/challenge` |
168
+| `siwe_message` | `string` | Yes | The SIWE message that was signed |
169
+| `siwe_signature` | `string` | Yes | Ethereum personal sign signature (hex) |
170
+| `reported_ip` | `string` | No | Client-reported public IP address |
171
+
172
+**Response fields:**
173
+
174
+| Field | Type | Description |
175
+|-------|------|-------------|
176
+| `identity` | `object` | Normalized identity (name + address) |
177
+| `expires_at` | `string` | ISO 8601 lease expiration |
178
+| `hostname` | `string` | Assigned tunnel hostname (e.g. `my-app.relay.example.com`) |
179
+| `access_token` | `string` | JWT access token for subsequent API calls |
180
+| `sni_port` | `int` | SNI port for QUIC transport (omitted if UDP not enabled) |
181
+| `udp_addr` | `string` | UDP address for QUIC transport (e.g. `relay.example.com:4443`) |
182
+| `udp_enabled` | `bool` | Whether UDP transport is active |
183
+| `tcp_addr` | `string` | Dedicated TCP address (e.g. `relay.example.com:10001`) |
184
+| `tcp_enabled` | `bool` | Whether TCP port transport is active |
185
+
186
+**Error codes:**
187
+
188
+| Code | Status | Description |
189
+|------|--------|-------------|
190
+| `unauthorized` | 403 | Invalid SIWE signature |
191
+| `hostname_conflict` | 409 | Hostname already registered |
192
+| `ip_banned` | 403 | Source IP is banned |
193
+| `udp_port_exhausted` | 503 | No UDP ports available |
194
+| `feature_unavailable` | 503 | Requested transport not available |
195
+
196
+**Example:**
197
+
198
+```bash
199
+curl -X POST https://relay.example.com/sdk/register \
200
+ -H "Content-Type: application/json" \
201
+ -d '{
202
+ "challenge_id": "abc123",
203
+ "siwe_message": "relay.example.com wants you to sign in...",
204
+ "siwe_signature": "0xdeadbeef..."
205
+ }'
206
+```
207
+
208
+**Response:**
209
+
210
+```json
211
+{
212
+ "ok": true,
213
+ "data": {
214
+ "identity": {
215
+ "name": "my-app",
216
+ "address": "0x1234567890abcdef1234567890abcdef12345678"
217
+ },
218
+ "expires_at": "2025-01-01T00:01:00Z",
219
+ "hostname": "my-app.relay.example.com",
220
+ "access_token": "eyJhbGciOiJFZDI1NTE5...",
221
+ "udp_enabled": false,
222
+ "tcp_enabled": false
223
+ }
224
+}
225
+```
226
+
227
+---
228
+
229
+### `POST /sdk/renew`
230
+
231
+Renew an existing lease to extend its TTL. Returns a new access token that should replace the old one.
232
+
233
+**Auth:** Access token (in request body)
234
+
235
+**Request body:**
236
+
237
+| Field | Type | Required | Description |
238
+|-------|------|----------|-------------|
239
+| `access_token` | `string` | Yes | Current JWT access token |
240
+| `ttl` | `int` | No | Requested TTL in seconds |
241
+| `reported_ip` | `string` | No | Client-reported public IP address |
242
+
243
+**Response fields:**
244
+
245
+| Field | Type | Description |
246
+|-------|------|-------------|
247
+| `expires_at` | `string` | ISO 8601 new expiration time |
248
+| `access_token` | `string` | New JWT access token (replaces old one) |
249
+
250
+**Error codes:**
251
+
252
+| Code | Status | Description |
253
+|------|--------|-------------|
254
+| `unauthorized` | 403 | Invalid or expired access token |
255
+| `lease_not_found` | 404 | No active lease for this identity |
256
+| `ip_banned` | 403 | Source IP is banned |
257
+
258
+**Example:**
259
+
260
+```bash
261
+curl -X POST https://relay.example.com/sdk/renew \
262
+ -H "Content-Type: application/json" \
263
+ -d '{
264
+ "access_token": "eyJhbGciOiJFZDI1NTE5...",
265
+ "ttl": 60
266
+ }'
267
+```
268
+
269
+**Response:**
270
+
271
+```json
272
+{
273
+ "ok": true,
274
+ "data": {
275
+ "expires_at": "2025-01-01T00:02:00Z",
276
+ "access_token": "eyJhbGciOiJFZDI1NTE5...new"
277
+ }
278
+}
279
+```
280
+
281
+---
282
+
283
+### `POST /sdk/unregister`
284
+
285
+Remove an active lease and release all associated resources (hostname, ports, connections).
286
+
287
+**Auth:** Access token (in request body)
288
+
289
+**Request body:**
290
+
291
+| Field | Type | Required | Description |
292
+|-------|------|----------|-------------|
293
+| `access_token` | `string` | Yes | Current JWT access token |
294
+
295
+**Response fields:**
296
+
297
+Empty data object on success.
298
+
299
+**Error codes:**
300
+
301
+| Code | Status | Description |
302
+|------|--------|-------------|
303
+| `unauthorized` | 403 | Invalid or expired access token |
304
+| `lease_not_found` | 404 | No active lease for this identity |
305
+
306
+**Example:**
307
+
308
+```bash
309
+curl -X POST https://relay.example.com/sdk/unregister \
310
+ -H "Content-Type: application/json" \
311
+ -d '{
312
+ "access_token": "eyJhbGciOiJFZDI1NTE5..."
313
+ }'
314
+```
315
+
316
+**Response:**
317
+
318
+```json
319
+{
320
+ "ok": true,
321
+ "data": {}
322
+}
323
+```
324
+
325
+---
326
+
327
+### `GET /sdk/connect`
328
+
329
+Establish a reverse tunnel connection using HTTP/1.1 connection hijacking. The SDK opens this connection and the relay holds it in a ready queue. When a client connects to the tunnel hostname via TLS SNI, the relay claims a ready connection and bridges traffic bidirectionally.
330
+
331
+**Auth:** `X-Portal-Access-Token` header
332
+
333
+**Requirements:**
334
+- Must use HTTP/1.1 (not HTTP/2)
335
+- Connection header must be `keep-alive`
336
+
337
+**Request headers:**
338
+
339
+| Header | Value | Description |
340
+|--------|-------|-------------|
341
+| `X-Portal-Access-Token` | `string` | JWT access token from registration |
342
+| `Connection` | `keep-alive` | Required for hijack |
343
+
344
+**Response:**
345
+
346
+On success, the server responds with `HTTP/1.1 200 OK` and hijacks the underlying TCP connection. No JSON body is returned — the connection is upgraded to a raw bidirectional TCP stream.
347
+
348
+**Error codes:**
349
+
350
+| Code | Status | Description |
351
+|------|--------|-------------|
352
+| `unauthorized` | 403 | Invalid or expired access token |
353
+| `lease_not_found` | 404 | No active lease for this identity |
354
+| `lease_rejected` | 403 | Lease not approved for routing |
355
+| `http11_only` | 505 | Must use HTTP/1.1 |
356
+| `hijack_unsupported` | 500 | Server does not support hijacking |
357
+| `hijack_failed` | 500 | Connection hijack failed |
358
+| `ip_banned` | 403 | Source IP is banned |
359
+
360
+**Example:**
361
+
362
+```bash
363
+curl -X GET https://relay.example.com/sdk/connect \
364
+ -H "X-Portal-Access-Token: eyJhbGciOiJFZDI1NTE5..." \
365
+ -H "Connection: keep-alive" \
366
+ --http1.1
367
+```
368
+
369
+> **Note:** In practice, the SDK does not use curl for this endpoint. It opens a raw TLS connection, writes the HTTP/1.1 request manually, reads the 200 response, and then uses the connection as a bidirectional TCP stream for tunneled traffic.
docs/src/routes/architecture/+page.md
renamed
+133
@@ -1,3 +1,126 @@
1
+---
2
+title: Architecture
3
+description: Portal system architecture, transport model, and protocol details.
4
+priority: P1
5
+---
6
+
7
+<script>
8
+import Mermaid from '$lib/components/Mermaid.svelte'
9
+
10
+const overviewDiagram = `flowchart TD
11
+ CB[Client Browser]
12
+ ETC[External TCP Client]
13
+ EUC[External UDP Client]
14
+
15
+ subgraph Relay["Relay Server"]
16
+ SNI["SNI Router :443"]
17
+ API["API Server / Control Plane"]
18
+ TCP["TCP Port Listener"]
19
+ QUIC["QUIC Datagram Listener"]
20
+ end
21
+
22
+ subgraph SDK["SDK / portal-tunnel"]
23
+ RC["Reverse Connect Handler"]
24
+ TLS["Tenant TLS Terminator"]
25
+ SDKUDP["UDP Forwarder"]
26
+ end
27
+
28
+ LS["Local Service"]
29
+ LU["Local UDP Service"]
30
+
31
+ CB -- TLS ClientHello --> SNI
32
+ SNI -- SNI route + 0x02 marker --> RC
33
+ RC --> TLS --> LS
34
+
35
+ ETC -- raw TCP --> TCP
36
+ TCP -- 0x01 marker + bridge --> RC
37
+
38
+ EUC -- UDP packet --> QUIC
39
+ QUIC -- QUIC DATAGRAM frame --> SDKUDP --> LU
40
+
41
+ SDK -- POST /sdk/register, GET /sdk/connect --> API`
42
+
43
+const tlsStreamDiagram = `sequenceDiagram
44
+ participant SDK as SDK / portal-tunnel
45
+ participant Relay as Relay Server
46
+ participant Client as Client Browser
47
+
48
+ SDK->>Relay: POST /sdk/register/challenge
49
+ Relay->>SDK: SIWE challenge message
50
+ SDK->>Relay: POST /sdk/register (signed)
51
+ Relay->>SDK: access_token + lease info
52
+
53
+ SDK->>Relay: GET /sdk/connect (HTTP/1.1 hijack)
54
+ Relay->>SDK: connection hijacked, 0x00 keepalives
55
+ Note over Relay: Session queued in per-lease stream ready queue
56
+
57
+ Client->>Relay: TLS ClientHello (SNI: name.relay.host)
58
+ Note over Relay: SNI peek, resolve lease, claim reverse session
59
+ Relay->>SDK: write 0x02 (TLS activation marker)
60
+ Note over SDK: Starts tenant TLS handshake locally via keyless signer
61
+ Relay->>Client: bridges raw encrypted bytes bidirectionally
62
+ Note over Client,SDK: End-to-end TLS, relay never sees plaintext`
63
+
64
+const tcpPortDiagram = `sequenceDiagram
65
+ participant SDK as SDK / portal-tunnel
66
+ participant Relay as Relay Server
67
+ participant ExtClient as External TCP Client
68
+
69
+ SDK->>Relay: POST /sdk/register (tcp_enabled=true, signed SIWE)
70
+ Note over Relay: Validates TCP plane enabled, allocates port from MIN_PORT-MAX_PORT
71
+ Relay->>SDK: tcp_addr + access_token
72
+
73
+ SDK->>Relay: GET /sdk/connect (reverse session, HTTP/1.1 hijack)
74
+ Note over Relay: Session queued in per-lease stream ready queue
75
+
76
+ ExtClient->>Relay: TCP connect to tcp_addr
77
+ Note over Relay: Accepts connection, claims reverse session
78
+ Relay->>SDK: write 0x01 (raw TCP activation marker)
79
+ Note over SDK: Receives 0x01, passes raw connection without TLS handshake
80
+ Relay->>ExtClient: bridges data bidirectionally (raw TCP)
81
+ Note over ExtClient,SDK: No TLS, pure raw TCP passthrough`
82
+
83
+const udpQuicDiagram = `sequenceDiagram
84
+ participant SDK as SDK / portal-tunnel
85
+ participant Relay as Relay Server
86
+ participant UDPClient as External UDP Client
87
+
88
+ SDK->>Relay: POST /sdk/register (udp_enabled=true, signed SIWE)
89
+ Note over Relay: Allocates UDP port from MIN_PORT-MAX_PORT
90
+ Relay->>SDK: udp_addr + access_token + sni_port
91
+
92
+ SDK->>Relay: QUIC connect to sni_port (ALPN: portal-tunnel, DATAGRAM enabled)
93
+ SDK->>Relay: Send access_token on first QUIC stream
94
+ Note over Relay: Validates token, registers QUIC tunnel for lease
95
+
96
+ UDPClient->>Relay: UDP packet to udp_addr
97
+ Note over Relay: Assigns flow ID, wraps in QUIC DATAGRAM frame
98
+ Relay->>SDK: QUIC DATAGRAM frame (flowID + payload)
99
+ Note over SDK: Decodes frame, forwards to local UDP service
100
+
101
+ SDK->>Relay: QUIC DATAGRAM frame (flowID + response)
102
+ Relay->>UDPClient: WriteToUDP back to original client address`
103
+
104
+const registrationDiagram = `sequenceDiagram
105
+ participant SDK as SDK / portal-tunnel
106
+ participant Relay as Relay Server
107
+
108
+ SDK->>Relay: POST /sdk/register/challenge (address)
109
+ Relay->>SDK: SIWE challenge message
110
+
111
+ Note over SDK: Signs SIWE message with secp256k1 identity key (personal_sign)
112
+
113
+ SDK->>Relay: POST /sdk/register (message, signature, name, tcp_enabled?, udp_enabled?)
114
+ Note over Relay: Validates SIWE signature, checks name availability
115
+ Note over Relay: Creates lease, publishes route at name.relay-host
116
+ Note over Relay: Allocates TCP/UDP ports if requested
117
+ Relay->>SDK: access_token (ES256K JWT) + lease info (tcp_addr?, udp_addr?, sni_port?)`
118
+</script>
119
+
120
+<div class="not-prose mb-8 rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-300">
121
+ <strong>Advanced Documentation</strong> — This page covers internal architecture details for contributors and advanced users.
122
+</div>
123
+
124
# Architecture
125
126
## Overview
@@ -27,6 +150,8 @@ UDP client
150
-> Local UDP service
151
```
152
153
+<Mermaid code={overviewDiagram} />
154
+
155
## Architecture Invariants
156
157
### Transport and Routing
@@ -122,6 +247,8 @@ Shared wire types, API envelope, error codes, path constants, and transport fram
247
248
Result: the relay decides routing, but tenant TLS termination still happens at the SDK/tunnel side.
249
250
+<Mermaid code={tlsStreamDiagram} />
251
+
252
### Tenant TLS Self-Probe Detection
253
254
1. After a real tenant connection begins I/O, the SDK may start one asynchronous self-probe for that listener if no probe is in flight and the 30-second cooldown has expired.
@@ -146,6 +273,8 @@ Result: this is a detect-only signal by default. It raises the cost of adaptive
273
274
Result: the relay allocates a dedicated TCP port per lease and bridges raw TCP without TLS. This is ideal for non-TLS protocols like Minecraft, game servers, or any raw TCP service.
275
276
+<Mermaid code={tcpPortDiagram} />
277
+
278
### UDP/QUIC Datagram Transport
279
280
1. SDK/tunnel requests a register challenge with `udp_enabled=true`, signs the returned SIWE message, and completes registration.
@@ -159,6 +288,8 @@ Result: the relay allocates a dedicated TCP port per lease and bridges raw TCP w
288
289
Result: raw public UDP exposure with an internal QUIC datagram backhaul. UDP and TCP port allocations are independent from the same `MIN_PORT-MAX_PORT` range.
290
291
+<Mermaid code={udpQuicDiagram} />
292
+
293
## WireGuard Overlay and Discovery
294
295
- Discovery bootstraps from public HTTPS relay URLs, then optionally synchronizes over WireGuard overlay.
@@ -179,6 +310,8 @@ Result: raw public UDP exposure with an internal QUIC datagram backhaul. UDP and
310
- TCP port registration has equivalent three-condition gating. Failures: `tcp_port_disabled` (403), `tcp_port_capacity_exceeded` (503), `tcp_port_exhausted` (503).
311
- `PORTAL_URL` is normalized to its host component only; path/query segments are ignored for routing.
312
313
+<Mermaid code={registrationDiagram} />
314
+
315
### 2. Reverse Connect
316
317
- `GET /sdk/connect` (HTTP/1.1 only, `X-Portal-Access-Token` header).
docs/src/routes/cli-reference/+page.md
new
+151
@@ -0,0 +1,151 @@
1
+---
2
+title: CLI Reference
3
+description: Complete reference for the Portal CLI commands, flags, and usage examples.
4
+---
5
+
6
+# CLI Reference
7
+
8
+The `portal` CLI exposes local services through Portal relay servers. This page documents all commands, flags, and usage patterns.
9
+
10
+## Installation
11
+
12
+### macOS / Linux
13
+
14
+```bash
15
+curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
16
+```
17
+
18
+### Windows (PowerShell)
19
+
20
+```powershell
21
+$ProgressPreference = 'SilentlyContinue'
22
+irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
23
+```
24
+
25
+### From a relay
26
+
27
+If your relay publishes its own installer:
28
+
29
+```bash
30
+curl -sSL https://portal.example.com/install.sh | bash
31
+```
32
+
33
+The installer downloads the `portal` binary and adds it to your PATH. No configuration file is written.
34
+
35
+## Commands
36
+
37
+### `portal expose`
38
+
39
+Expose a local service to the internet.
40
+
41
+```bash
42
+portal expose [flags] <target>
43
+```
44
+
45
+**Target formats:**
46
+
47
+| Format | Example | Resolves to |
48
+|--------|---------|-------------|
49
+| Bare port | `3000` | `127.0.0.1:3000` |
50
+| Host:port | `localhost:8080` | `localhost:8080` |
51
+| URL | `http://127.0.0.1:3000` | `127.0.0.1:3000` |
52
+
53
+Instead of a positional target, you can use `--http-route` for multi-service routing.
54
+
55
+**Flags:**
56
+
57
+| Flag | Type | Default | Description |
58
+|------|------|---------|-------------|
59
+| `--relays` | string | _(registry)_ | Portal relay API URLs (comma-separated, https only) |
60
+| `--discovery` | bool | `true` | Include public registry relays and discover additional bootstraps |
61
+| `--ban-mitm` | bool | `true` | Ban relay when the MITM self-probe detects TLS termination |
62
+| `--identity-path` | string | `./identity.json` | Identity JSON file path; created automatically when missing |
63
+| `--name` | string | _(auto)_ | Public hostname prefix (single DNS label); auto-generated when omitted |
64
+| `--description` | string | | Service description metadata |
65
+| `--tags` | string | | Service tags metadata (comma-separated) |
66
+| `--thumbnail` | string | | Service thumbnail URL metadata |
67
+| `--owner` | string | | Service owner metadata |
68
+| `--hide` | bool | `false` | Hide service from relay listing screens |
69
+| `--tcp` | bool | `false` | Request a dedicated TCP port for raw TCP services (no TLS) |
70
+| `--http-route` | string | | HTTP route mapping in `PATH=UPSTREAM` form; repeat for multiple routes |
71
+
72
+**Examples:**
73
+
74
+Basic usage:
75
+
76
+```bash
77
+portal expose 3000
78
+```
79
+
80
+With custom name and relay:
81
+
82
+```bash
83
+portal expose localhost:8080 \
84
+ --name myapp \
85
+ --relays https://portal.example.com \
86
+ --description "My web application" \
87
+ --tags webapp,demo
88
+```
89
+
90
+TCP port routing (Minecraft server):
91
+
92
+```bash
93
+portal expose localhost:25565 --name minecraft --tcp
94
+```
95
+
96
+Multi-service HTTP routing:
97
+
98
+```bash
99
+portal expose --name myapp \
100
+ --http-route /api=http://127.0.0.1:3001 \
101
+ --http-route /=http://127.0.0.1:5173
102
+```
103
+
104
+Route matching is longest-prefix-first. `/api` matches `/api/*` and strips the `/api` prefix before proxying to the upstream. Routed HTTP mode automatically forwards `X-Forwarded-*` headers, rewrites upstream `Location` redirects, and remaps cookie paths.
105
+
106
+Disable relay discovery:
107
+
108
+```bash
109
+portal expose 3000 --relays https://portal.example.com --discovery=false
110
+```
111
+
112
+Warning-only MITM mode:
113
+
114
+```bash
115
+portal expose 3000 --ban-mitm=false
116
+```
117
+
118
+### `portal list`
119
+
120
+Print the relay URLs that the CLI will use.
121
+
122
+```bash
123
+portal list [flags]
124
+```
125
+
126
+**Flags:**
127
+
128
+| Flag | Type | Default | Description |
129
+|------|------|---------|-------------|
130
+| `--relays` | string | _(registry)_ | Additional relay URLs |
131
+| `--default-relays` | bool | `true` | Include public registry relays |
132
+
133
+Unlike `portal expose`, `portal list` does not run the relay discovery expansion loop. It only resolves the registry seed list plus explicit `--relays` values.
134
+
135
+## Behavior Notes
136
+
137
+- **Identity persistence** — `portal expose` loads or creates a signing identity at `identity.json` (or `--identity-path`). Reusing the same path keeps the same address across runs.
138
+- **Multiple relays** — Multiple relay URLs are registered independently. Each relay gets its own lease. A relay going down does not stop healthy relays from serving.
139
+- **Retry semantics** — Relay startup and reconnect failures are retried in the background. The tunnel starts as soon as relay URLs pass local validation.
140
+- **Discovery expansion** — With discovery enabled, the relay list can grow beyond the initial registry + `--relays` values through relay-to-relay synchronization.
141
+- **MITM enforcement** — Enabled by default. The TLS self-probe runs asynchronously after real connections begin, with a 30-second cooldown between probes.
142
+- **503 on unreachable local service** — When the local target is unreachable, the tunnel returns an HTTP 503 page to the client.
143
+- **HTTP route mode** — Cannot be combined with `--udp`. Routes are HTTP-only.
144
+- **TCP port requirements** — `--tcp` requires the relay to have `TCP_ENABLED=true`, a valid `MIN_PORT/MAX_PORT` range, and TCP port enabled in the admin panel.
145
+- **Legacy CLI removed** — Bare `portal [flags]` is no longer accepted; use `portal expose` explicitly. `APP_*`, `RELAYS`, and `DEFAULT_RELAYS` environment variables are no longer used.
146
+
147
+## Next Steps
148
+
149
+- **[Getting Started](/getting-started)** — Quick tutorial for your first tunnel
150
+- **[Concepts](/concepts)** — How Portal's encryption and relay model works
151
+- **[Deployment](/deployment)** — Run your own relay server
docs/src/routes/concepts/+page.md
new
+129
@@ -0,0 +1,129 @@
1
+---
2
+title: Concepts
3
+description: Understand how Portal provides trustless, end-to-end encrypted tunnels.
4
+---
5
+
6
+# Concepts
7
+
8
+Portal publishes local services on public HTTPS URLs through relay servers. Unlike most tunnel solutions, **the relay never sees your plaintext traffic**. This page explains how.
9
+
10
+## What is Portal?
11
+
12
+Portal is a reverse tunnel that exposes your local applications to the internet. It differs from alternatives like ngrok or Cloudflare Tunnel in one key way: **relays are trustless**. You don't need to trust the relay operator because they can't read your traffic.
13
+
14
+This means you can:
15
+- Use any public relay without security concerns
16
+- Run your own relay for full control
17
+- Switch between relays freely — your security model doesn't depend on the relay operator
18
+
19
+## The Trustless Relay Model
20
+
21
+In a traditional tunnel, the relay terminates TLS and has full access to your plaintext:
22
+
23
+```text
24
+Client → [TLS] → Relay (decrypts, re-encrypts) → [TLS] → Your App
25
+ ↑ Relay sees plaintext
26
+```
27
+
28
+Portal works differently. The relay only routes encrypted traffic:
29
+
30
+```text
31
+Client → [TLS] ────────────────────────────── → Your App
32
+ ↑ Relay forwards raw bytes
33
+ (cannot decrypt)
34
+```
35
+
36
+The relay reads only the TLS ClientHello (the initial unencrypted handshake message) to extract the SNI hostname for routing. After that, it bridges raw encrypted bytes between the client and your local TLS server.
37
+
38
+## End-to-End TLS
39
+
40
+Here's the detailed flow:
41
+
42
+1. **SNI routing** — A client connects to the relay on port 443. The relay peeks at the TLS ClientHello to read the SNI hostname (e.g., `myapp.relay.example.com`).
43
+
44
+2. **Reverse session claim** — The relay looks up which tunnel owns that hostname and claims one of its waiting reverse sessions.
45
+
46
+3. **Local TLS termination** — Your Portal client receives the connection and performs the TLS handshake locally. Session keys are derived on your machine — the relay never receives them.
47
+
48
+4. **Keyless signing** — For relay-hosted domains, Portal uses the relay's `/v1/sign` endpoint to get certificate signatures. The relay acts as a "keyless" signing oracle — it signs handshake digests but never receives the resulting session keys.
49
+
50
+5. **Encrypted data flow** — After the handshake, all traffic flows as encrypted bytes through the relay. The relay continues forwarding without needing plaintext.
51
+
52
+**Result:** TLS terminates on your side. The relay provides routing and certificate signing only.
53
+
54
+## MITM Detection
55
+
56
+How do you know the relay is actually forwarding encrypted bytes and not terminating TLS itself? Portal includes a built-in self-probe mechanism:
57
+
58
+1. After a real connection is established, Portal opens a separate TLS connection to its own public URL
59
+2. The probe exports TLS keying material on both the client side and the server side
60
+3. If the exported values match, the connection was passed through without relay-side termination
61
+4. A mismatch indicates the relay may be terminating TLS (suspected MITM)
62
+
63
+By default, `portal expose` enables strict enforcement — if the probe detects TLS termination, the relay is banned. You can use `--ban-mitm=false` for warning-only mode.
64
+
65
+> **Note:** The self-probe is a detect-only signal. It raises the cost of relay-side TLS termination but cannot prove passthrough for every user connection.
66
+
67
+## Transport Models
68
+
69
+Portal supports three transport modes:
70
+
71
+### TLS Passthrough (default)
72
+
73
+Standard HTTPS tunneling. Client connects to port 443, relay routes by SNI, your app terminates TLS locally.
74
+
75
+```text
76
+Client → Relay :443 (SNI routing) → Reverse Session → Your App (TLS termination)
77
+```
78
+
79
+### Raw TCP Port Routing
80
+
81
+For non-TLS services like Minecraft servers or database connections. The relay allocates a dedicated TCP port and bridges raw TCP without any TLS wrapping.
82
+
83
+```text
84
+Client → Relay :40001 (dedicated TCP port) → Reverse Session → Your App (raw TCP)
85
+```
86
+
87
+Enable with `portal expose --tcp`. Requires the relay to have TCP port transport enabled.
88
+
89
+### UDP via QUIC
90
+
91
+For UDP services. The relay allocates a UDP port and uses an internal QUIC tunnel to carry datagrams between the client and your app.
92
+
93
+```text
94
+UDP Client → Relay :40002 (UDP) → QUIC Tunnel → Your App (raw UDP)
95
+```
96
+
97
+UDP and TCP port allocations are independent — the same numeric range can serve both.
98
+
99
+## Identity and Authentication
100
+
101
+Portal uses **SIWE (Sign-In with Ethereum)** for identity:
102
+
103
+- On first run, Portal generates a secp256k1 key pair stored as `identity.json`
104
+- Registration uses a challenge/response flow — you sign a message proving key ownership
105
+- No accounts, no API keys, no email required
106
+- Reuse the same `--identity-path` across runs to maintain your identity
107
+
108
+The relay issues a lease-scoped JWT access token after registration, used for all subsequent operations (renew, connect, unregister).
109
+
110
+## Relay Discovery
111
+
112
+Portal maintains a public relay registry at:
113
+
114
+```text
115
+https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json
116
+```
117
+
118
+When you run `portal expose`, the CLI:
119
+1. Loads the registry seed list
120
+2. Adds any explicit `--relays` URLs
121
+3. Optionally discovers additional relays through relay-to-relay synchronization
122
+
123
+Use `--discovery=false` to limit connections to only explicitly specified relays.
124
+
125
+## Next Steps
126
+
127
+- **[CLI Reference](/cli-reference)** — Full command and flag documentation
128
+- **[Architecture](/architecture)** — Deep dive into system design and protocol details
129
+- **[Getting Started](/getting-started)** — Quick install and first tunnel setup
docs/src/routes/configuration/+page.md
new
+200
@@ -0,0 +1,200 @@
1
+---
2
+title: Configuration Reference
3
+description: Complete reference for all Portal environment variables, CLI flags, and configuration files.
4
+---
5
+
6
+# Configuration Reference
7
+
8
+Complete reference for all Portal environment variables, CLI flags, and configuration files.
9
+
10
+## Relay Server Environment Variables
11
+
12
+The relay server (`relay-server`) reads configuration from environment variables. Each variable corresponds to a CLI flag of the same shape (e.g. `PORTAL_URL` → `--portal-url`). CLI flags take precedence over environment variables when both are set.
13
+
14
+### Core
15
+
16
+| Variable | Default | Type | Description |
17
+|----------|---------|------|-------------|
18
+| `PORTAL_URL` | `https://localhost:4017` | string | Public base URL of this relay server |
19
+| `API_PORT` | `4017` | int | Admin/API server listen port |
20
+| `SNI_PORT` | `443` | int | TCP SNI router listen port |
21
+
22
+### Transport
23
+
24
+| Variable | Default | Type | Description |
25
+|----------|---------|------|-------------|
26
+| `MIN_PORT` | `0` | int | Inclusive minimum port for UDP and raw TCP transports (`0` = disabled) |
27
+| `MAX_PORT` | `0` | int | Inclusive maximum port for UDP and raw TCP transports (`0` = disabled) |
28
+| `UDP_ENABLED` | `false` | bool | Enable UDP relay transport; requires a valid `MIN_PORT`/`MAX_PORT` range |
29
+| `TCP_ENABLED` | `false` | bool | Enable raw TCP port transport; requires a valid `MIN_PORT`/`MAX_PORT` range |
30
+
31
+### Features
32
+
33
+| Variable | Default | Type | Description |
34
+|----------|---------|------|-------------|
35
+| `LANDING_PAGE_ENABLED` | `false` | bool | Enable the landing page by default when no admin setting has been saved yet |
36
+| `DISCOVERY` | `false` | bool | Serve relay discovery endpoints and poll discovery peers |
37
+| `BOOTSTRAPS` | `""` | string | Additional bootstrap relay API URLs used for discovery expansion (comma-separated) |
38
+
39
+### Identity
40
+
41
+| Variable | Default | Type | Description |
42
+|----------|---------|------|-------------|
43
+| `IDENTITY_PATH` | `identity.json` | string | Relay identity JSON file path |
44
+| `ADMIN_SECRET_KEY` | `""` | string | Admin authentication secret. When empty, the server auto-generates a random key at startup and logs it. |
45
+
46
+### Proxy
47
+
48
+| Variable | Default | Type | Description |
49
+|----------|---------|------|-------------|
50
+| `TRUST_PROXY_HEADERS` | `false` | bool | Trust `X-Forwarded-*` and `X-Real-IP` headers from trusted proxies |
51
+| `TRUSTED_PROXY_CIDRS` | `""` | string | Trusted proxy CIDR allowlist for forwarded headers (comma-separated); defaults to private/loopback ranges when `TRUST_PROXY_HEADERS` is enabled |
52
+
53
+### TLS
54
+
55
+| Variable | Default | Type | Description |
56
+|----------|---------|------|-------------|
57
+| `KEYLESS_DIR` | `./.portal-certs` | string | Directory path for relay keyless TLS materials |
58
+| `ACME_DNS_PROVIDER` | `""` | string | ACME DNS provider for managed DNS-01/A-record sync and ENS gasless DNSSEC/TXT automation (`cloudflare` \| `gcloud` \| `route53`); leave empty to use manual `fullchain.pem`/`privatekey.pem` from `KEYLESS_DIR` |
59
+| `ENS_GASLESS_ENABLED` | `false` | bool | Enable ENS gasless DNS import automation for the managed DNS zone and lease hostnames |
60
+
61
+### Admin
62
+
63
+| Variable | Default | Type | Description |
64
+|----------|---------|------|-------------|
65
+| `ADMIN_SETTINGS_PATH` | `admin_settings.json` | string | Admin settings file path for persisted admin state |
66
+| `HEADLESS_SHELL_URL` | `""` | string | Headless Chrome CDP WebSocket URL for thumbnail generation (e.g. `ws://headless-shell:9222`) |
67
+
68
+### Cloudflare
69
+
70
+| Variable | Default | Type | Description |
71
+|----------|---------|------|-------------|
72
+| `CLOUDFLARE_TOKEN` | | string | Cloudflare DNS API token; required when `ACME_DNS_PROVIDER=cloudflare` |
73
+
74
+### Google Cloud
75
+
76
+| Variable | Aliases | Default | Type | Description |
77
+|----------|---------|---------|------|-------------|
78
+| `GCP_PROJECT_ID` | `GOOGLE_CLOUD_PROJECT`, `GCLOUD_PROJECT`, `GCE_PROJECT` | | string | Google Cloud project ID for Cloud DNS automation; auto-detected from ADC or GCE metadata when omitted |
79
+| `GCP_MANAGED_ZONE` | `GCP_ZONE`, `GCE_ZONE_ID` | | string | Explicit Google Cloud DNS managed zone name or numeric ID override |
80
+| `GOOGLE_APPLICATION_CREDENTIALS` | | | string | Path to GCP service account key file (standard ADC; used by the GCP client library) |
81
+
82
+### AWS
83
+
84
+| Variable | Aliases | Default | Type | Description |
85
+|----------|---------|---------|------|-------------|
86
+| `AWS_ACCESS_KEY_ID` | | | string | AWS access key ID for Route53 static credentials; uses the default AWS credential chain when omitted |
87
+| `AWS_SECRET_ACCESS_KEY` | | | string | AWS secret access key for Route53 static credentials |
88
+| `AWS_SESSION_TOKEN` | | | string | AWS session token for Route53 temporary credentials |
89
+| `AWS_REGION` | `AWS_DEFAULT_REGION` | `us-east-1` | string | AWS region for Route53 and Route53-backed DNS-01 |
90
+| `AWS_HOSTED_ZONE_ID` | | | string | Explicit Route53 hosted zone ID override |
91
+| `AWS_DNSSEC_KMS_KEY_ARN` | | | string | AWS KMS key ARN used to create a Route53 DNSSEC key-signing key when needed |
92
+
93
+---
94
+
95
+## Portal Tunnel CLI Flags
96
+
97
+The `portal expose` subcommand accepts the following flags. Flags that read from environment variables are noted in the **Env Var** column.
98
+
99
+### Connection
100
+
101
+| Flag | Env Var | Type | Default | Description |
102
+|------|---------|------|---------|-------------|
103
+| `--relays` | | string | _(registry)_ | Additional Portal relay server API URLs (comma-separated; scheme omitted defaults to https) |
104
+| `--discovery` | | bool | `true` | Include public registry relays and discover additional relay bootstraps |
105
+| `--ban-mitm` | `BAN_MITM` | bool | `true` | Ban relay when the MITM self-probe detects TLS termination |
106
+
107
+### Identity
108
+
109
+| Flag | Env Var | Type | Default | Description |
110
+|------|---------|------|---------|-------------|
111
+| `--identity-path` | `IDENTITY_PATH` | string | `identity.json` | Identity JSON file path |
112
+| `--identity-json` | `IDENTITY_JSON` | string | | Identity JSON payload; overrides `--identity-path` contents and is persisted there when both are set |
113
+
114
+### Lease
115
+
116
+| Flag | Env Var | Type | Default | Description |
117
+|------|---------|------|---------|-------------|
118
+| `--name` | | string | _(auto)_ | Public hostname prefix (single DNS label); auto-generated when omitted |
119
+| `--description` | | string | | Service description metadata |
120
+| `--tags` | | string | | Service tags metadata (comma-separated) |
121
+| `--owner` | | string | | Service owner metadata |
122
+| `--thumbnail` | | string | | Service thumbnail URL metadata |
123
+| `--hide` | | bool | `false` | Hide service from relay listing screens |
124
+
125
+### Routing
126
+
127
+| Flag | Env Var | Type | Default | Description |
128
+|------|---------|------|---------|-------------|
129
+| `--http-route` | | string | | HTTP route mapping in `PATH=UPSTREAM` form; repeat to aggregate multiple local HTTP services behind one public URL |
130
+
131
+### Transport
132
+
133
+| Flag | Env Var | Type | Default | Description |
134
+|------|---------|------|---------|-------------|
135
+| `--udp` | `UDP_ENABLED` | bool | `false` | Enable public UDP relay in addition to the default TCP relay |
136
+| `--udp-addr` | `UDP_ADDR` | string | | Local UDP target address for relayed datagrams (`host:port` or port only); defaults to the target when `--udp` is enabled |
137
+| `--tcp` | `TCP_ENABLED` | bool | `false` | Request a dedicated TCP port on the relay for raw TCP services (no TLS; e.g., Minecraft, game servers) |
138
+
139
+The `portal list` subcommand accepts the following flags:
140
+
141
+| Flag | Type | Default | Description |
142
+|------|------|---------|-------------|
143
+| `--relays` | string | _(registry)_ | Additional Portal relay server API URLs (comma-separated) |
144
+| `--default-relays` | bool | `true` | Include public registry relays |
145
+
146
+---
147
+
148
+## Configuration Files
149
+
150
+### `identity.json`
151
+
152
+Stores the cryptographic identity used to sign tunnel sessions. Created automatically by both `relay-server` and `portal expose` on first run.
153
+
154
+| Field | Type | Description |
155
+|-------|------|-------------|
156
+| `name` | string | Human-readable label for this identity |
157
+| `address` | string | Derived public address (used as the tunnel subdomain seed) |
158
+| `public_key` | string | Ed25519 public key (base64) |
159
+| `private_key` | string | Ed25519 private key (base64); keep secret |
160
+
161
+The file path is controlled by `IDENTITY_PATH` / `--identity-path`. The same file can be shared across restarts to keep a stable address.
162
+
163
+### `admin_settings.json`
164
+
165
+Persists admin-panel state for the relay server. Managed automatically by the relay on write; do not edit manually while the server is running.
166
+
167
+The file path is controlled by `ADMIN_SETTINGS_PATH` / `--admin-settings-path`.
168
+
169
+---
170
+
171
+## ACME DNS Provider Configuration
172
+
173
+Set `ACME_DNS_PROVIDER` (or `--acme-dns-provider`) to one of the values below to enable automated TLS certificate issuance via DNS-01 challenges.
174
+
175
+When this variable is empty the relay server falls back to manually supplied `fullchain.pem` and `privatekey.pem` files in `KEYLESS_DIR`.
176
+
177
+### Cloudflare (`cloudflare`)
178
+
179
+| Variable | Required | Description |
180
+|----------|----------|-------------|
181
+| `CLOUDFLARE_TOKEN` | Yes | Cloudflare DNS API token with `Zone:DNS:Edit` permission |
182
+
183
+### Google Cloud DNS (`gcloud`)
184
+
185
+| Variable | Required | Description |
186
+|----------|----------|-------------|
187
+| `GCP_PROJECT_ID` | No | Google Cloud project ID; auto-detected from ADC or GCE metadata when omitted |
188
+| `GCP_MANAGED_ZONE` | No | Cloud DNS managed zone name or numeric ID; inferred from the portal domain when omitted |
189
+| `GOOGLE_APPLICATION_CREDENTIALS` | No | Path to a service account key JSON file; uses Application Default Credentials when omitted |
190
+
191
+### AWS Route53 (`route53`)
192
+
193
+| Variable | Required | Description |
194
+|----------|----------|-------------|
195
+| `AWS_ACCESS_KEY_ID` | No | Access key ID; uses the default AWS credential chain (instance profile, env, `~/.aws/credentials`) when omitted |
196
+| `AWS_SECRET_ACCESS_KEY` | No | Secret access key; required when `AWS_ACCESS_KEY_ID` is set |
197
+| `AWS_SESSION_TOKEN` | No | Session token for temporary credentials |
198
+| `AWS_REGION` | No | AWS region; defaults to `us-east-1` |
199
+| `AWS_HOSTED_ZONE_ID` | No | Route53 hosted zone ID; inferred from the portal domain when omitted |
200
+| `AWS_DNSSEC_KMS_KEY_ARN` | No | KMS key ARN for DNSSEC key-signing key creation |
docs/src/routes/deployment/+page.md
renamed
+10
@@ -1,3 +1,13 @@
1
+---
2
+title: Deployment
3
+description: Production deployment guide for Portal relay servers.
4
+priority: P1
5
+---
6
+
7
+<div class="not-prose mb-8 rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800 dark:border-blue-800 dark:bg-blue-950/30 dark:text-blue-300">
8
+ <strong>Advanced Documentation</strong> — This page covers production relay deployment for operators.
9
+</div>
10
+
11
# Portal Relay Deployment Guide
12
13
This guide covers the production steps for running Portal Relay on a public domain.
docs/src/routes/getting-started/+page.md
new
+104
@@ -0,0 +1,104 @@
1
+---
2
+title: Getting Started
3
+description: Install Portal and expose your first local service to the internet.
4
+---
5
+
6
+# Getting Started
7
+
8
+This guide walks you through installing Portal and exposing your first local service to the public internet in under a minute.
9
+
10
+## Prerequisites
11
+
12
+- A local application running on any port (e.g., a web server on port 3000)
13
+- macOS, Linux, or Windows
14
+- Internet connectivity
15
+
16
+## Install the CLI
17
+
18
+### macOS / Linux
19
+
20
+```bash
21
+curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash
22
+```
23
+
24
+### Windows (PowerShell)
25
+
26
+```powershell
27
+$ProgressPreference = 'SilentlyContinue'
28
+irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex
29
+```
30
+
31
+The installer downloads the `portal` binary and places it in your PATH. No configuration file is created — Portal works out of the box.
32
+
33
+## Expose Your First App
34
+
35
+Start a local application (or use any existing one), then run:
36
+
37
+```bash
38
+portal expose 3000
39
+```
40
+
41
+Replace `3000` with whatever port your application is running on. Portal accepts:
42
+- A bare port: `3000` (resolves to `127.0.0.1:3000`)
43
+- A host:port: `localhost:8080`
44
+- A URL: `http://127.0.0.1:3000`
45
+
46
+Portal will output a public HTTPS URL like:
47
+
48
+```text
49
+https://your-name.relay.example.com
50
+```
51
+
52
+Open that URL in any browser — you're now accessing your local app through an encrypted tunnel.
53
+
54
+## What Just Happened?
55
+
56
+When you ran `portal expose`:
57
+
58
+1. **Identity created** — Portal generated a signing identity at `./identity.json` (reuse it across runs to keep the same address)
59
+2. **Relay discovery** — Portal found available public relays from the official registry
60
+3. **Lease registered** — Your service was registered with a relay using SIWE (Sign-In with Ethereum) authentication
61
+4. **Reverse session** — A persistent connection was established from your machine to the relay
62
+5. **TLS provisioned** — The relay provided certificate signing, but TLS terminates locally on your machine (end-to-end encryption)
63
+
64
+The relay never sees your plaintext traffic. See [Concepts](/concepts) to understand how this works.
65
+
66
+## Customizing Your Tunnel
67
+
68
+### Set a custom hostname
69
+
70
+```bash
71
+portal expose 3000 --name myapp
72
+```
73
+
74
+Your app will be available at `myapp.relay.example.com`.
75
+
76
+### Use a specific relay
77
+
78
+```bash
79
+portal expose 3000 --relays https://portal.example.com --discovery=false
80
+```
81
+
82
+### Expose a TCP service (e.g., Minecraft)
83
+
84
+```bash
85
+portal expose localhost:25565 --name minecraft --tcp
86
+```
87
+
88
+This allocates a dedicated TCP port on the relay — no TLS wrapping, perfect for game servers.
89
+
90
+### Multi-service HTTP routing
91
+
92
+```bash
93
+portal expose --name myapp \
94
+ --http-route /api=http://127.0.0.1:3001 \
95
+ --http-route /=http://127.0.0.1:5173
96
+```
97
+
98
+Routes are matched longest-prefix-first. The `/api` prefix is stripped before proxying.
99
+
100
+## Next Steps
101
+
102
+- **[Concepts](/concepts)** — Understand Portal's trustless relay model and end-to-end encryption
103
+- **[CLI Reference](/cli-reference)** — Complete command and flag documentation
104
+- **[Deployment](/deployment)** — Run your own relay server
docs/src/routes/self-hosting/+page.md
new
+173
@@ -0,0 +1,173 @@
1
+---
2
+title: Self-Hosting
3
+description: Run your own Portal relay for private tunneling.
4
+---
5
+
6
+# Self-Hosting Guide
7
+
8
+This guide is for developers who want their own relay for a single project or team — not a platform operator managing relay infrastructure for many users. If you need production-grade deployment with managed TLS, ACME automation, or multi-tenant relay infrastructure, see the [Deployment Guide](/deployment) instead.
9
+
10
+You should have a relay running and accepting tunnel connections in about 10 minutes.
11
+
12
+## Prerequisites
13
+
14
+- Docker installed on your server
15
+- A Linux server with a static public IP
16
+- A domain name you control (e.g. `relay.example.com`)
17
+- Inbound ports open on your server:
18
+ - `443/tcp` — SNI router (tunnel traffic)
19
+ - `4017/tcp` — Admin/API port (tunnel registration)
20
+
21
+## Quick Start
22
+
23
+Run the relay with a single Docker command:
24
+
25
+```bash
26
+docker run -d \
27
+ --name portal-relay \
28
+ --restart unless-stopped \
29
+ -p 443:443 \
30
+ -p 4017:4017 \
31
+ -e PORTAL_URL=https://relay.example.com:4017 \
32
+ -e ADMIN_SECRET_KEY=changeme \
33
+ -v $(pwd)/relay-data:/data \
34
+ ghcr.io/gosuda/portal-tunnel/relay-server:latest
35
+```
36
+
37
+Replace `relay.example.com` with your domain and set a strong `ADMIN_SECRET_KEY`.
38
+
39
+## Docker Compose Setup
40
+
41
+For a more maintainable setup, use Docker Compose:
42
+
43
+```yaml
44
+# compose.yml
45
+services:
46
+ relay:
47
+ image: ghcr.io/gosuda/portal-tunnel/relay-server:latest
48
+ restart: unless-stopped
49
+ ports:
50
+ - "443:443"
51
+ - "4017:4017"
52
+ environment:
53
+ PORTAL_URL: https://relay.example.com:4017
54
+ API_PORT: "4017"
55
+ SNI_PORT: "443"
56
+ ADMIN_SECRET_KEY: changeme
57
+ IDENTITY_PATH: /data/identity.json
58
+ volumes:
59
+ - ./relay-data:/data
60
+```
61
+
62
+Start it:
63
+
64
+```bash
65
+docker compose up -d
66
+```
67
+
68
+### Key Environment Variables
69
+
70
+| Variable | Default | Description |
71
+|---|---|---|
72
+| `PORTAL_URL` | `https://localhost:4017` | Public base URL of your relay. Tunnels use this to register. |
73
+| `API_PORT` | `4017` | Admin/API server port. |
74
+| `SNI_PORT` | `443` | TCP SNI router port for tunnel traffic. |
75
+| `ADMIN_SECRET_KEY` | _(empty)_ | Secret for admin API access. Set this. |
76
+| `IDENTITY_PATH` | `identity.json` | Path to the relay's identity file (auto-created on first run). |
77
+
78
+## Connecting Your Tunnel
79
+
80
+Point `portal-tunnel` at your relay with the `--relays` flag:
81
+
82
+```bash
83
+portal expose --relays https://relay.example.com:4017 localhost:3000
84
+```
85
+
86
+The `--relays` flag accepts a comma-separated list of relay API URLs. If you omit the scheme, `https` is assumed.
87
+
88
+To avoid typing `--relays` every time, use a shell alias:
89
+
90
+```bash
91
+alias portal-relay='portal expose --relays https://relay.example.com:4017'
92
+portal-relay localhost:3000
93
+```
94
+
95
+## DNS Configuration
96
+
97
+Tunnels are assigned subdomains under your relay domain (e.g. `abc123.relay.example.com`). You need a wildcard DNS record pointing to your server:
98
+
99
+| Type | Name | Value |
100
+|---|---|---|
101
+| `A` | `*.relay.example.com` | `<your server IP>` |
102
+| `A` | `relay.example.com` | `<your server IP>` |
103
+
104
+DNS propagation typically takes a few minutes but can take up to 48 hours depending on your provider.
105
+
106
+## Optional: TLS with ACME
107
+
108
+By default the relay expects you to place `fullchain.pem` and `privatekey.pem` in the `KEYLESS_DIR` directory (`.portal-certs` by default). For automatic certificate management via DNS-01 challenges, set `ACME_DNS_PROVIDER`:
109
+
110
+```yaml
111
+environment:
112
+ ACME_DNS_PROVIDER: cloudflare # or: gcloud, route53
113
+ CLOUDFLARE_TOKEN: <your-token>
114
+```
115
+
116
+See the [Deployment Guide](/deployment) for full ACME configuration options, credential setup per provider, and managed DNS automation.
117
+
118
+## Optional: Enable TCP/UDP Tunneling
119
+
120
+To relay raw TCP or UDP traffic (game servers, databases, etc.), enable the transports and set a port range:
121
+
122
+```yaml
123
+environment:
124
+ TCP_ENABLED: "true"
125
+ UDP_ENABLED: "true"
126
+ MIN_PORT: "10000"
127
+ MAX_PORT: "10100"
128
+ports:
129
+ - "10000-10100:10000-10100/tcp"
130
+ - "10000-10100:10000-10100/udp"
131
+```
132
+
133
+See [TCP/UDP Tunneling](/tcp-udp-tunneling) for usage details.
134
+
135
+## Troubleshooting
136
+
137
+**Port already in use**
138
+
139
+Port `443` is commonly taken by another process. Check what's listening:
140
+
141
+```bash
142
+sudo ss -tlnp | grep ':443'
143
+```
144
+
145
+Stop the conflicting service or change `SNI_PORT` and update your firewall rules accordingly.
146
+
147
+**DNS not resolving**
148
+
149
+Verify your wildcard record is live before connecting a tunnel:
150
+
151
+```bash
152
+dig +short test.relay.example.com
153
+```
154
+
155
+If nothing returns, check your DNS provider dashboard and allow more time for propagation.
156
+
157
+**Firewall blocking connections**
158
+
159
+Ensure both ports are open in your cloud provider's security group or firewall:
160
+
161
+```bash
162
+# UFW example
163
+sudo ufw allow 443/tcp
164
+sudo ufw allow 4017/tcp
165
+```
166
+
167
+**Certificate errors**
168
+
169
+If you see TLS errors on the client side, confirm your certificate files are present in `KEYLESS_DIR` and that `fullchain.pem` includes the full chain (leaf + intermediates). If using ACME, check the relay logs for DNS provider authentication errors:
170
+
171
+```bash
172
+docker compose logs relay --tail 50
173
+```
docs/src/routes/tcp-udp-tunneling/+page.md
new
+158
@@ -0,0 +1,158 @@
1
+---
2
+title: TCP/UDP Tunneling
3
+description: Tunnel raw TCP and UDP services like game servers through Portal.
4
+---
5
+
6
+# TCP/UDP Tunneling
7
+
8
+Portal supports raw TCP and UDP tunneling in addition to its default HTTPS proxy mode. This lets you expose services that don't speak HTTP — game servers, databases, custom binary protocols, and anything that needs a dedicated port on the public internet.
9
+
10
+## Overview
11
+
12
+By default, Portal routes traffic through TLS SNI multiplexing on port 443 — perfect for web services. For everything else:
13
+
14
+- **Raw TCP** — allocates a dedicated public port on the relay. Clients connect to `relay-host:port` with no TLS wrapping, exactly as if the service were directly internet-facing.
15
+- **UDP** — transports datagrams over a QUIC backhaul between the client and your local service. Ideal for game servers, VoIP, and other latency-sensitive protocols.
16
+
17
+Both modes require the relay server to have a port range configured and the corresponding transport enabled.
18
+
19
+## Relay Configuration
20
+
21
+Enable TCP and UDP transports on your relay by setting these environment variables:
22
+
23
+| Variable | Default | Description |
24
+|---|---|---|
25
+| `TCP_ENABLED` | `false` | Enable raw TCP port allocation |
26
+| `UDP_ENABLED` | `false` | Enable UDP/QUIC datagram transport |
27
+| `MIN_PORT` | `0` (disabled) | Inclusive minimum of the port allocation range |
28
+| `MAX_PORT` | `0` (disabled) | Inclusive maximum of the port allocation range |
29
+
30
+`MIN_PORT` and `MAX_PORT` are shared between TCP and UDP. Both transports draw from the same pool. Setting either to `0` disables port allocation entirely.
31
+
32
+<div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 0.75rem 1rem; border-radius: 0.375rem; margin: 1rem 0;">
33
+ <strong>Note:</strong> The relay's firewall or cloud security group must allow inbound traffic on the entire <code>MIN_PORT</code>–<code>MAX_PORT</code> range, for both TCP and UDP protocols, depending on which transports you enable.
34
+</div>
35
+
36
+## TCP Tunneling
37
+
38
+Raw TCP tunneling allocates a dedicated port on the relay. Incoming TCP connections to that port are bridged directly to your local service with no TLS.
39
+
40
+### Step 1 — Configure the relay
41
+
42
+```bash
43
+TCP_ENABLED=true
44
+MIN_PORT=10000
45
+MAX_PORT=20000
46
+```
47
+
48
+### Step 2 — Expose your local service
49
+
50
+```bash
51
+portal expose --tcp --name myapp localhost:8080
52
+```
53
+
54
+Portal registers a lease and receives an allocated port from the relay's range. The output will show the assigned port:
55
+
56
+```text
57
+TCP port: relay.example.com:12345
58
+```
59
+
60
+### Step 3 — Connect clients
61
+
62
+Clients connect directly to the relay host and allocated port:
63
+
64
+```text
65
+relay.example.com:12345
66
+```
67
+
68
+No special client software is needed — any TCP client works.
69
+
70
+## UDP Tunneling
71
+
72
+UDP tunneling transports datagrams over a QUIC backhaul. The relay listens on an allocated UDP port and forwards datagrams to your local UDP service.
73
+
74
+### Step 1 — Configure the relay
75
+
76
+```bash
77
+UDP_ENABLED=true
78
+MIN_PORT=10000
79
+MAX_PORT=20000
80
+```
81
+
82
+### Step 2 — Expose your local service
83
+
84
+Use `--udp` to enable UDP, and `--udp-addr` to specify the local UDP target address:
85
+
86
+```bash
87
+portal expose --udp --udp-addr localhost:19132 --name myapp localhost:8080
88
+```
89
+
90
+- `--udp-addr` — the local UDP address that receives relayed datagrams. Defaults to the primary target address when `--udp` is set without `--udp-addr`.
91
+- The primary target (`localhost:8080` above) is still used for any TCP/HTTP traffic on the same lease.
92
+
93
+## Minecraft Server Example
94
+
95
+This is a complete walkthrough for exposing a Minecraft Java Edition server (`localhost:25565`) to the public internet.
96
+
97
+### Relay docker-compose snippet
98
+
99
+```yaml
100
+services:
101
+ relay:
102
+ image: ghcr.io/gosuda/portal-tunnel/relay-server:latest
103
+ environment:
104
+ TCP_ENABLED: "true"
105
+ MIN_PORT: "10000"
106
+ MAX_PORT: "20000"
107
+ ports:
108
+ - "443:443"
109
+ - "4017:4017"
110
+ - "10000-20000:10000-20000" # TCP port range
111
+```
112
+
113
+<div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 0.75rem 1rem; border-radius: 0.375rem; margin: 1rem 0;">
114
+ <strong>Note:</strong> Docker's port range syntax (<code>10000-20000:10000-20000</code>) creates one mapping per port. Keep ranges reasonably small — large ranges (10 000+ ports) can slow Docker startup.
115
+</div>
116
+
117
+### Expose the server
118
+
119
+```bash
120
+portal expose --tcp --name minecraft localhost:25565
121
+```
122
+
123
+Output:
124
+
125
+```text
126
+TCP port: relay.example.com:13742
127
+```
128
+
129
+### Connect in Minecraft
130
+
131
+In the Minecraft multiplayer screen, add a server with the address:
132
+
133
+```text
134
+relay.example.com:13742
135
+```
136
+
137
+That's it. The port number will remain the same as long as the tunnel is active and the lease is held by the same identity.
138
+
139
+## Combining TCP + UDP
140
+
141
+A single lease can carry both a raw TCP port and a UDP relay simultaneously. This is useful for servers that use both protocols — for example, a game server that streams data over TCP and sends position updates over UDP.
142
+
143
+```bash
144
+portal expose --tcp --udp --udp-addr localhost:19132 localhost:25565
145
+```
146
+
147
+This registers one lease that gets:
148
+- A dedicated TCP port for `localhost:25565`
149
+- A dedicated UDP port forwarding datagrams to `localhost:19132`
150
+
151
+Both ports are drawn from the same `MIN_PORT`–`MAX_PORT` range on the relay.
152
+
153
+## Limitations
154
+
155
+- **Port range capacity** — the relay can serve at most `MAX_PORT - MIN_PORT + 1` concurrent TCP+UDP leases. Plan the range accordingly.
156
+- **No TLS on raw TCP** — the raw TCP path has no TLS wrapping. Traffic between the relay and connecting clients is unencrypted. Use application-level encryption (e.g. SSH, WireGuard) if the service requires confidentiality.
157
+- **UDP max packet size** — datagrams are capped at **1350 bytes** (`DefaultMaxPacketSize`). Packets larger than this are dropped. Most game protocols fit within this limit, but verify if you use a custom protocol.
158
+- **Flow idle timeout** — UDP flows that have seen no traffic for **30 seconds** are cleaned up on the relay. Long-lived connections should send keepalive packets if they may be idle.
docs/static/.nojekyll
docs/static/CNAME
new
+1
@@ -0,0 +1 @@
1
+docs.example.com
docs/static/examples/nginx-proxy-multi-service/.env.example
renamed
docs/static/examples/nginx-proxy-multi-service/docker-compose.yaml
renamed
docs/static/examples/nginx-proxy-multi-service/nginx.conf
renamed
docs/static/examples/nginx-proxy/.env.example
renamed
docs/static/examples/nginx-proxy/deploy_portal.sh
renamed
docs/static/examples/nginx-proxy/docker-compose.yaml
renamed
docs/static/examples/nginx-proxy/nginx.conf
renamed
docs/static/examples/nginx-proxy/nginx_deploy.sh
renamed
docs/static/examples/nginx-proxy/watch_and_deploy.sh
renamed
docs/svelte.config.js
new
+27
@@ -0,0 +1,27 @@
1
+import adapter from '@sveltejs/adapter-static';
2
+import { mdsvex } from 'mdsvex';
3
+
4
+/** @type {import('@sveltejs/kit').Config} */
5
+const config = {
6
+ extensions: ['.svelte', '.md'],
7
+ preprocess: [
8
+ mdsvex({
9
+ extensions: ['.md']
10
+ })
11
+ ],
12
+ kit: {
13
+ adapter: adapter({
14
+ pages: 'build',
15
+ assets: 'build',
16
+ strict: true
17
+ }),
18
+ paths: {
19
+ base: process.env.BASE_PATH || ''
20
+ },
21
+ prerender: {
22
+ handleMissingId: 'warn'
23
+ }
24
+ }
25
+};
26
+
27
+export default config;
docs/tsconfig.json
new
+14
@@ -0,0 +1,14 @@
1
+{
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "checkJs": true,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "moduleResolution": "bundler"
13
+ }
14
+}
docs/vite.config.ts
new
+7
@@ -0,0 +1,7 @@
1
+import tailwindcss from '@tailwindcss/vite';
2
+import { sveltekit } from '@sveltejs/kit/vite';
3
+import { defineConfig } from 'vite';
4
+
5
+export default defineConfig({
6
+ plugins: [tailwindcss(), sveltekit()]
7
+});