docs: restore OpenClaw-hosted ClawHub docs (#2631)

* Revert "fix: serve docs subdomain root (#2630)"

This reverts commit 9ef33726b8.

* Revert "feat: move docs to canonical subdomain"

This reverts commit 6afb91b772.

* Revert "docs: serve ClawHub docs with shared OpenClaw renderer (#2609)"

This reverts commit 71156751ff.

* fix: restore ClawHub email docs host

* test: preserve non-docs validation fixes

* test: remove stale docs proxy test

* docs: keep ClawHub contributor references current

* fix: keep docs auth callback outside docs routes
This commit is contained in:
Patrick Erichsen
2026-06-14 16:05:34 -07:00
committed by GitHub
parent 9ef33726b8
commit fdb4d312f4
49 changed files with 553 additions and 1726 deletions
@@ -0,0 +1,36 @@
name: OpenClaw Docs Sync Dispatch
on:
push:
branches:
- main
paths:
- docs/**
- .github/workflows/openclaw-docs-sync-dispatch.yml
workflow_dispatch:
permissions:
contents: read
jobs:
dispatch-openclaw-docs-sync:
runs-on: ubuntu-latest
steps:
- name: Dispatch OpenClaw docs sync
env:
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
run: |
set -euo pipefail
if [ -z "${OPENCLAW_DOCS_SYNC_TOKEN:-}" ]; then
echo "::error::OPENCLAW_DOCS_SYNC_TOKEN is required to dispatch openclaw/openclaw docs sync."
exit 1
fi
curl --fail-with-body --silent --show-error \
--request POST \
--header "Authorization: Bearer ${OPENCLAW_DOCS_SYNC_TOKEN}" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/openclaw/openclaw/actions/workflows/docs-sync-publish.yml/dispatches \
--data '{"ref":"main"}'
-1
View File
@@ -9,7 +9,6 @@ artifacts/
bin/docs-list
dist
dist-ssr
public/docs/
!packages/schema/dist
!packages/schema/dist/**
*.local
-6
View File
@@ -1,11 +1,5 @@
# Changelog
## Unreleased
### Changes
- Docs: serve ClawHub documentation from `docs.clawhub.ai` and permanently redirect legacy `clawhub.ai/docs` URLs while preserving paths and queries.
## 0.21.0 - 2026-06-11
### Changes
+20 -103
View File
@@ -76,7 +76,6 @@
"jsdom": "29.1.1",
"nitro": "3.0.260429-beta",
"only-allow": "1.2.2",
"openclaw-docs-site": "github:openclaw/docs#8e2588e6d718a3c25499688bbb9ad9f0ccba3677",
"oxfmt": "0.54.0",
"oxlint": "1.69.0",
"oxlint-tsgolint": "0.23.0",
@@ -223,7 +222,7 @@
"@csstools/css-calc": ["@csstools/css-calc@3.2.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg=="],
"@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.2", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-n6Zd8mpVhObnaOqq6TC/lBCKgYncAGfFwuvJGQZTDRAwEoxwXIZu9kXBQeXkcqHsE6Sp6LyxDMrvXj5gOxnryw=="],
"@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.1", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g=="],
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
@@ -379,7 +378,7 @@
"@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="],
@@ -549,20 +548,6 @@
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.69.0", "", { "os": "win32", "cpu": "x64" }, "sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA=="],
"@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="],
"@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw=="],
"@pagefind/freebsd-x64": ["@pagefind/freebsd-x64@1.5.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA=="],
"@pagefind/linux-arm64": ["@pagefind/linux-arm64@1.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw=="],
"@pagefind/linux-x64": ["@pagefind/linux-x64@1.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA=="],
"@pagefind/windows-arm64": ["@pagefind/windows-arm64@1.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g=="],
"@pagefind/windows-x64": ["@pagefind/windows-x64@1.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg=="],
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
"@playwright/test": ["@playwright/test@1.60.0", "", { "dependencies": { "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" } }, "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag=="],
@@ -687,32 +672,6 @@
"@react-email/ui": ["@react-email/ui@6.6.0", "", { "dependencies": { "esbuild": "0.28.0", "next": "16.2.6" } }, "sha512-PCAUtbN9WQEb5mC71xTtgXQFXOKyit6UpuRhzAw8h8O3WBTtkTfvFwZHHXqThvVfissm6TtiGCNY/9WdNziQ8w=="],
"@resvg/resvg-js": ["@resvg/resvg-js@2.6.2", "", { "optionalDependencies": { "@resvg/resvg-js-android-arm-eabi": "2.6.2", "@resvg/resvg-js-android-arm64": "2.6.2", "@resvg/resvg-js-darwin-arm64": "2.6.2", "@resvg/resvg-js-darwin-x64": "2.6.2", "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", "@resvg/resvg-js-linux-arm64-musl": "2.6.2", "@resvg/resvg-js-linux-x64-gnu": "2.6.2", "@resvg/resvg-js-linux-x64-musl": "2.6.2", "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", "@resvg/resvg-js-win32-x64-msvc": "2.6.2" } }, "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q=="],
"@resvg/resvg-js-android-arm-eabi": ["@resvg/resvg-js-android-arm-eabi@2.6.2", "", { "os": "android", "cpu": "arm" }, "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA=="],
"@resvg/resvg-js-android-arm64": ["@resvg/resvg-js-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ=="],
"@resvg/resvg-js-darwin-arm64": ["@resvg/resvg-js-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A=="],
"@resvg/resvg-js-darwin-x64": ["@resvg/resvg-js-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw=="],
"@resvg/resvg-js-linux-arm-gnueabihf": ["@resvg/resvg-js-linux-arm-gnueabihf@2.6.2", "", { "os": "linux", "cpu": "arm" }, "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw=="],
"@resvg/resvg-js-linux-arm64-gnu": ["@resvg/resvg-js-linux-arm64-gnu@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg=="],
"@resvg/resvg-js-linux-arm64-musl": ["@resvg/resvg-js-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg=="],
"@resvg/resvg-js-linux-x64-gnu": ["@resvg/resvg-js-linux-x64-gnu@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw=="],
"@resvg/resvg-js-linux-x64-musl": ["@resvg/resvg-js-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ=="],
"@resvg/resvg-js-win32-arm64-msvc": ["@resvg/resvg-js-win32-arm64-msvc@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ=="],
"@resvg/resvg-js-win32-ia32-msvc": ["@resvg/resvg-js-win32-ia32-msvc@2.6.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w=="],
"@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="],
"@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.6.2", "", {}, "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gCYzGOSkYY6Z034suzd20euvds7lPzMEEla62DJGE/ZAlR4OMBnNbvnBSsIGUCAr52gaWMsloGxP4tVGtN5aCA=="],
@@ -935,14 +894,8 @@
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
"@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
@@ -1007,7 +960,7 @@
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.4", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA=="],
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg=="],
"atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="],
@@ -1019,7 +972,7 @@
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.35", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.34", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw=="],
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
@@ -1027,7 +980,7 @@
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="],
"caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
@@ -1083,7 +1036,7 @@
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
"crossws": ["crossws@0.4.6", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-/Wxe9Z007EbJ496j88nToZEvyPZ8PY/wjZJ18Agh/GCA9cYHyLbxtrpdFlFzAw3TV20F0SUYGl0g6PzChbwUrg=="],
"crossws": ["crossws@0.4.5", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA=="],
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
@@ -1205,7 +1158,7 @@
"dot-prop": ["dot-prop@10.1.0", "", { "dependencies": { "type-fest": "^5.0.0" } }, "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q=="],
"electron-to-chromium": ["electron-to-chromium@1.5.371", "", {}, "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w=="],
"electron-to-chromium": ["electron-to-chromium@1.5.368", "", {}, "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw=="],
"engine.io": ["engine.io@6.6.8", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1" } }, "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g=="],
@@ -1217,7 +1170,7 @@
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
"env-runner": ["env-runner@0.1.11", "", { "dependencies": { "crossws": "^0.4.5", "exsolve": "^1.0.8", "httpxy": "^0.5.3", "srvx": "^0.11.16" }, "peerDependencies": { "@netlify/runtime": "^4.1.23", "@vercel/queue": "^0.2.0", "miniflare": "^4.20260515.0" }, "optionalPeers": ["@netlify/runtime", "@vercel/queue", "miniflare"], "bin": { "env-runner": "dist/cli.mjs" } }, "sha512-fJfI87qPE7DxBD4EwYUuAlokWBOj9AnE6BmQxezCIQGBge/D7rp0kNTuOzSyJyjiQSGvFdpv/SPrOokPlTfA4Q=="],
"env-runner": ["env-runner@0.1.9", "", { "dependencies": { "crossws": "^0.4.5", "exsolve": "^1.0.8", "httpxy": "^0.5.3", "srvx": "^0.11.15" }, "peerDependencies": { "@netlify/runtime": "^4.1.23", "@vercel/queue": "^0.2.0", "miniflare": "^4.20260515.0" }, "optionalPeers": ["@netlify/runtime", "@vercel/queue", "miniflare"], "bin": { "env-runner": "dist/cli.mjs" } }, "sha512-W9AiZlPx0uXtghAJiTBkeZOgyQdecVvoln3cHoOEZswPq0cVMi+WBhUQjdUn+JcZFAFgOt+i5fcO7C2zniZoCg=="],
"es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="],
@@ -1229,8 +1182,6 @@
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
@@ -1241,8 +1192,6 @@
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
@@ -1275,8 +1224,6 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
"h3": ["h3@2.0.1-rc.22", "", { "dependencies": { "rou3": "^0.8.1", "srvx": "^0.11.15" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA=="],
"h3-v2": ["h3@2.0.1-rc.20", "", { "dependencies": { "rou3": "^0.8.1", "srvx": "^0.11.13" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"], "bin": { "h3": "bin/h3.mjs" } }, "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg=="],
@@ -1305,8 +1252,6 @@
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
"hookable": ["hookable@6.1.1", "", {}, "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ=="],
"html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
@@ -1339,8 +1284,6 @@
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
"is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
@@ -1353,7 +1296,7 @@
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"isbot": ["isbot@5.1.42", "", {}, "sha512-/SXsVh7KpPRISrD4ffrGSxnTLlUBzEQUfWIusaJPrpJ93FW1P0YEZri5vAUkFsA0m2HRUhQRQadk2wJ+EeKowQ=="],
"isbot": ["isbot@5.1.41", "", {}, "sha512-9WFV/Vhh0FEj6CQ7MoHweEL9/vLKPjeoD2I2htbAjX7kbW7VJs3OCpWOVyd+JraNTWVU6/DRx2MZy2KaUNXHcg=="],
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
@@ -1361,13 +1304,13 @@
"istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
"jsdom": ["jsdom@29.1.1", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.3.5", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q=="],
@@ -1385,8 +1328,6 @@
"khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"launch-editor": ["launch-editor@2.14.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.4" } }, "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA=="],
@@ -1419,8 +1360,6 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"linkify-it": ["linkify-it@5.0.1", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg=="],
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
"log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
@@ -1441,10 +1380,6 @@
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
"markdown-it": ["markdown-it@14.2.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.1", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ=="],
"markdown-it-anchor": ["markdown-it-anchor@9.2.0", "", { "peerDependencies": { "@types/markdown-it": "*", "markdown-it": "*" } }, "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
@@ -1481,8 +1416,6 @@
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
"mermaid": ["mermaid@11.15.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "es-toolkit": "^1.45.1", "katex": "^0.16.25", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
@@ -1581,7 +1514,7 @@
"obug": ["obug@2.1.2", "", {}, "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg=="],
"ocache": ["ocache@0.1.5", "", { "dependencies": { "ohash": "^2.0.11" } }, "sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w=="],
"ocache": ["ocache@0.1.4", "", { "dependencies": { "ohash": "^2.0.11" } }, "sha512-e7geNdWjxSnvsSgvLuPvgKgu7ubM10ZmTPOgpr7mz2BXYtvjMKTiLhjFi/gWU8chkuP6hNkZBsa9LzOusyaqkQ=="],
"ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
@@ -1595,8 +1528,6 @@
"only-allow": ["only-allow@1.2.2", "", { "dependencies": { "which-pm-runs": "1.1.0" }, "bin": { "only-allow": "bin.js" } }, "sha512-uxyNYDsCh5YIJ780G7hC5OHjVUr9reHsbZNMM80L9tZlTpb3hUzb36KXgW4ZUGtJKQnGA3xegmWg1BxhWV0jJA=="],
"openclaw-docs-site": ["openclaw-docs-site@github:openclaw/docs#8e2588e", { "dependencies": { "@resvg/resvg-js": "^2.6.2", "gray-matter": "4.0.3", "highlight.js": "11.11.1", "markdown-it": "14.2.0", "markdown-it-anchor": "9.2.0", "mermaid": "11.15.0", "pagefind": "1.5.2" } }, "openclaw-docs-8e2588e", "sha512-HHLEMPCsEUVsRsMbvwoTQaaIuwMAwl2YegX8wcYW5bnTmWINTtRcFPWY4r7uGLyPFFeXxMsu3+yKaANuZIBdQQ=="],
"ora": ["ora@9.4.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ=="],
"oxc-parser": ["oxc-parser@0.120.0", "", { "dependencies": { "@oxc-project/types": "^0.120.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.120.0", "@oxc-parser/binding-android-arm64": "0.120.0", "@oxc-parser/binding-darwin-arm64": "0.120.0", "@oxc-parser/binding-darwin-x64": "0.120.0", "@oxc-parser/binding-freebsd-x64": "0.120.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.120.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.120.0", "@oxc-parser/binding-linux-arm64-gnu": "0.120.0", "@oxc-parser/binding-linux-arm64-musl": "0.120.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-musl": "0.120.0", "@oxc-parser/binding-linux-s390x-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-musl": "0.120.0", "@oxc-parser/binding-openharmony-arm64": "0.120.0", "@oxc-parser/binding-wasm32-wasi": "0.120.0", "@oxc-parser/binding-win32-arm64-msvc": "0.120.0", "@oxc-parser/binding-win32-ia32-msvc": "0.120.0", "@oxc-parser/binding-win32-x64-msvc": "0.120.0" } }, "sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w=="],
@@ -1611,8 +1542,6 @@
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"pagefind": ["pagefind@1.5.2", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.5.2", "@pagefind/darwin-x64": "1.5.2", "@pagefind/freebsd-x64": "1.5.2", "@pagefind/linux-arm64": "1.5.2", "@pagefind/linux-x64": "1.5.2", "@pagefind/windows-arm64": "1.5.2", "@pagefind/windows-x64": "1.5.2" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q=="],
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
"parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="],
@@ -1651,7 +1580,7 @@
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
"prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="],
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
@@ -1663,8 +1592,6 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
"react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
@@ -1723,8 +1650,6 @@
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"semver": ["semver@7.8.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg=="],
@@ -1761,8 +1686,6 @@
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"srvx": ["srvx@0.11.16", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw=="],
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
@@ -1783,8 +1706,6 @@
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
"stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
@@ -1843,8 +1764,6 @@
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
@@ -1953,22 +1872,18 @@
"@react-email/components/@react-email/render": ["@react-email/render@2.0.6", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog=="],
"@tailwindcss/node/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tanstack/router-generator/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"@tanstack/router-plugin/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"@tanstack/router-utils/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
@@ -1981,6 +1896,8 @@
"babel-dead-code-elimination/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
"babel-dead-code-elimination/@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
"convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="],
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
@@ -1999,13 +1916,13 @@
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"jsdom/undici": ["undici@7.27.1", "", {}, "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"magicast/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"make-dir/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="],
"mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
@@ -2017,14 +1934,14 @@
"react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
"react-email/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"rolldown/@oxc-project/types": ["@oxc-project/types@0.134.0", "", {}, "sha512-T0xuRRKrQFmocH8y+jGfpmSkGcheaJExY9lEihmR1Gm2aH+75B8CzgU2rABRQSzzDxLjZ15Sc0bRVLj5lVeNXQ=="],
"strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"vite/rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="],
"xmlbuilder2/js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
"@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
"@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
+11 -10
View File
@@ -13,7 +13,7 @@ function expectFooterLinksUnderlined(html: string) {
/href="https:\/\/clawhub\.ai"[^>]*style="[^"]*color:#8a8a8e[^"]*text-decoration[^"]*underline[^"]*"[^>]*>ClawHub<\/a>/,
);
expect(html).toMatch(
/href="https:\/\/(?:docs\.clawhub\.ai|docs\.openclaw\.ai)"[^>]*style="[^"]*color:#8a8a8e[^"]*text-decoration[^"]*underline[^"]*"[^>]*>Docs<\/a>/,
/href="https:\/\/(?:clawhub\.ai\/docs|docs\.openclaw\.ai\/clawhub)"[^>]*style="[^"]*color:#8a8a8e[^"]*text-decoration[^"]*underline[^"]*"[^>]*>Docs<\/a>/,
);
}
@@ -57,10 +57,10 @@ describe("moderation notification email copy", () => {
expect(email.html).not.toContain("If you already appealed");
expect(email.html).not.toContain("separate support email");
expect(email.html).not.toContain("You received this email because");
expect(email.html).toContain("https://docs.clawhub.ai");
expect(email.html).toContain("https://docs.openclaw.ai/clawhub");
expectFooterLinksUnderlined(email.html);
expect(email.text).not.toContain("clawhub scan ./my-skill --output clawhub-scan.zip");
expect(email.text).not.toContain("https://docs.clawhub.ai/cli#scan-path");
expect(email.text).not.toContain("https://docs.openclaw.ai/clawhub/cli#scan-path");
});
it("does not leak raw manual moderator notes into outbound email", async () => {
@@ -113,7 +113,7 @@ describe("moderation notification email copy", () => {
expect(email.html).toContain("ACCOUNT REINSTATED");
expect(email.html).toContain("API tokens issued before the suspension");
expect(email.html).not.toContain("You received this email because");
expect(email.html).toContain("https://docs.clawhub.ai");
expect(email.html).toContain("https://docs.openclaw.ai/clawhub");
expectFooterLinksUnderlined(email.html);
});
@@ -148,7 +148,7 @@ describe("moderation notification email copy", () => {
expect(email.text).toContain("Version: 1.2.3");
expect(email.text).toContain("clawhub scan download demo-skill --version 1.2.3");
expect(email.text).toContain("Increment the version number before uploading the fixed skill.");
expect(email.text).toContain("https://docs.clawhub.ai/moderation");
expect(email.text).toContain("https://docs.openclaw.ai/clawhub/moderation");
expect(email.text).not.toContain("clawhub scan ./my-skill --output clawhub-scan.zip");
expect(email.text).not.toContain("fixed local copy");
expect(email.text).toContain("Repeated malicious rejections may lead to account disablement");
@@ -219,7 +219,8 @@ describe("moderation notification email copy", () => {
scanSource: "publish",
authorRemediation: {
summary: "Replace the legacy before_agent_start hook with current prompt hooks.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
],
@@ -242,7 +243,7 @@ describe("moderation notification email copy", () => {
);
expect(email.text).toContain(" Docs:");
expect(email.text).toContain(
" https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
" https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
);
expect(email.text).not.toContain("ClawHub Security");
expect(email.html).toContain("Validate a local fix");
@@ -251,7 +252,7 @@ describe("moderation notification email copy", () => {
expect(email.html).not.toContain('href="https://clawhub.ai" style="display:inline-block');
expect(email.html).not.toContain("You&#39;re receiving this because");
expect(email.html).not.toContain("You're receiving this because");
expect(email.html).toContain("https://docs.clawhub.ai/plugin-validation-fixes");
expect(email.html).toContain("https://docs.openclaw.ai");
expectFooterLinksUnderlined(email.html);
expect(email.html).toContain("OpenClaw Version");
expect(email.html).toContain("0.9.0");
@@ -350,7 +351,7 @@ describe("moderation notification email copy", () => {
expect(email.html).toContain("Open appeal");
expect(email.html).not.toContain("<package>");
expect(email.html).not.toContain("You received this email because");
expect(email.html).toContain("https://docs.clawhub.ai");
expect(email.html).toContain("https://docs.openclaw.ai/clawhub");
expectFooterLinksUnderlined(email.html);
});
@@ -369,7 +370,7 @@ describe("moderation notification email copy", () => {
expect(email.html).not.toContain("{{primary_action_url}}");
expect(email.html).not.toContain('href="https://clawhub.ai" style="display:inline-block');
expect(email.html).not.toContain("You received this email because");
expect(email.html).toContain("https://docs.clawhub.ai");
expect(email.html).toContain("https://docs.openclaw.ai/clawhub");
expectFooterLinksUnderlined(email.html);
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
export const APPEALS_URL = "https://appeals.openclaw.ai/";
export const MODERATION_GUIDELINES_URL = "https://docs.clawhub.ai/moderation";
export const MODERATION_GUIDELINES_URL = "https://docs.openclaw.ai/clawhub/moderation";
export const MALICIOUS_REJECTION_ACCOUNT_WARNING =
"Repeated malicious rejections may lead to account disablement.";
const MAX_EMAIL_FINDING_SUMMARY_LENGTH = 280;
+3 -2
View File
@@ -35,7 +35,8 @@ describe("package inspector publish normalization", () => {
authorRemediation: {
summary:
"Publish the entrypoint declared in OpenClaw package metadata or update the metadata to point at an existing file.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#package-entrypoint-missing",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#package-entrypoint-missing",
},
},
],
@@ -75,7 +76,7 @@ describe("package inspector publish normalization", () => {
authorRemediation: {
summary: "Declare the OpenClaw plugin API range this package supports.",
docsUrl:
"https://docs.clawhub.ai/plugin-validation-fixes#package-plugin-api-compat-missing",
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#package-plugin-api-compat-missing",
},
},
],
+1 -1
View File
@@ -41,7 +41,7 @@ type InspectorReport = {
issues?: unknown[];
};
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.clawhub.ai/plugin-validation-fixes";
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.openclaw.ai/clawhub/plugin-validation-fixes";
const LEGACY_AUTHOR_REMEDIATION_SUMMARIES = {
"channel-env-vars":
+18 -11
View File
@@ -6757,7 +6757,8 @@ describe("packages public queries", () => {
evidence: ["src/index.ts:4"],
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
],
@@ -6851,7 +6852,8 @@ describe("packages public queries", () => {
message: "legacy before_agent_start hook is deprecated",
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
}),
],
@@ -6911,7 +6913,8 @@ describe("packages public queries", () => {
fixture: "demo",
authorRemediation: {
summary: "Add a display name to the plugin manifest.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#manifest-name-missing",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#manifest-name-missing",
},
},
],
@@ -6926,7 +6929,7 @@ describe("packages public queries", () => {
inspectorFindingId: "demo:manifest-name-missing",
authorRemediation: {
summary: "Add a display name to the plugin manifest.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#manifest-name-missing",
docsUrl: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#manifest-name-missing",
},
}),
);
@@ -6982,7 +6985,8 @@ describe("packages public queries", () => {
fixture: "demo",
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
],
@@ -7040,7 +7044,8 @@ describe("packages public queries", () => {
fixture: "demo",
authorRemediation: {
summary: "Replace registerTool with the current plugin API.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#missing-expected-seam",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#missing-expected-seam",
},
},
],
@@ -7108,7 +7113,8 @@ describe("packages public queries", () => {
fixture: "demo",
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
],
@@ -7123,7 +7129,8 @@ describe("packages public queries", () => {
issueClass: "deprecation-warning",
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
}),
);
@@ -7148,7 +7155,7 @@ describe("packages public queries", () => {
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl:
"https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
]),
@@ -7240,7 +7247,7 @@ describe("packages public queries", () => {
authorRemediation: {
summary: "Replace registerTool with the current plugin API.",
docsUrl:
"https://docs.clawhub.ai/plugin-validation-fixes#missing-expected-seam",
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#missing-expected-seam",
},
createdAt: 2,
},
@@ -7410,7 +7417,7 @@ describe("packages public queries", () => {
authorRemediation: {
summary: "Move prompt mutation work to before_prompt_build.",
docsUrl:
"https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
createdAt: 1,
},
+1 -1
View File
@@ -301,7 +301,7 @@ const resolvePublishTargetForUserInternalHandler = (
>
)._handler;
function indexedRows(rows: unknown[]) {
function indexedRows<T>(rows: T[]) {
return {
collect: vi.fn(async () => rows),
order: vi.fn(() => ({
+1 -1
View File
@@ -18,7 +18,7 @@ const resolveHandler = (
>
)._handler;
function chain(value: unknown) {
function chain<T>(value: T) {
const q = {
eq: vi.fn(() => q),
};
+6 -9
View File
@@ -7,8 +7,8 @@ read_when:
# Docs
`docs/` is the publishable source for user-facing ClawHub documentation served
at `https://docs.clawhub.ai`.
`docs/` is the publishable source for user-facing ClawHub pages that can be
mirrored into the `ClawHub` tab on `docs.openclaw.ai`.
Keep product, CLI, publisher, API, policy, security, and troubleshooting docs
here. These pages should explain how people use ClawHub: discover, install,
@@ -17,8 +17,8 @@ publish, inspect, report, moderate, and integrate with the registry.
Use `specs/` for repository setup, production deploy runbooks, implementation
plans, design rationale, regression notes, maintainer validation records, and
internal subsystem intent. If a page tells someone how to run or deploy the
ClawHub project itself, it belongs in `specs/`, not in the public ClawHub docs
site.
ClawHub project itself, it belongs in `specs/`, not in the public OpenClaw docs
tab.
Reading order:
@@ -47,8 +47,5 @@ Maintainer records:
Publish flow:
- Build with `bun run docs:build`; this stages `docs/` into the shared
`openclaw/docs` renderer and writes the generated site to `public/docs`.
- Preview the generated static site with `bun run docs:run`.
- Validate the generated route with `bun run docs:smoke`.
- `specs/` is intentionally not published.
- Changes under `docs/` dispatch the OpenClaw docs sync workflow, which mirrors this directory into the `ClawHub` tab on `docs.openclaw.ai`.
- `specs/` is intentionally not mirrored.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

-71
View File
@@ -1,71 +0,0 @@
{
"$schema": "https://mintlify.com/docs.json",
"name": "ClawHub",
"description": "Public documentation for finding, installing, publishing, and operating ClawHub skills and plugins.",
"favicon": "/assets/clawd-logo.png",
"logo": {
"light": "/assets/clawd-logo.png",
"dark": "/assets/clawd-logo.png"
},
"colors": {
"primary": "#FF8A5F"
},
"chat": {
"name": "Molty",
"label": "ClawHub docs assistant"
},
"navbar": {
"links": [
{
"label": "ClawHub",
"href": "https://clawhub.ai",
"icon": "globe"
}
]
},
"search": {
"placeholder": "Search ClawHub docs...",
"suggestions": ["Install a skill", "Publish a package", "Use API tokens", "Run security audits"]
},
"navigation": {
"languages": [
{
"language": "en",
"tabs": [
{
"tab": "Start",
"groups": [
{
"group": "Overview",
"pages": ["index", "quickstart", "how-it-works"]
}
]
},
{
"tab": "Use",
"groups": [
{
"group": "Install and Publish",
"pages": ["cli", "publishing", "skill-format", "plugin-validation-fixes"]
},
{
"group": "Reference",
"pages": ["auth", "api", "http-api", "telemetry", "troubleshooting"]
}
]
},
{
"tab": "Trust",
"groups": [
{
"group": "Safety and Operations",
"pages": ["security", "security-audits", "moderation", "acceptable-usage"]
}
]
}
]
}
]
},
"redirects": []
}
+1 -1
View File
@@ -14,7 +14,7 @@ import {
import type { ReactNode } from "react";
export const CLAWHUB_URL = "https://clawhub.ai";
export const CLAWHUB_DOCS_URL = "https://docs.clawhub.ai";
export const CLAWHUB_DOCS_URL = "https://docs.openclaw.ai/clawhub";
export const EMAIL_PREFERENCES_URL = "https://clawhub.ai/settings";
export const APPEALS_URL = "https://appeals.openclaw.ai/";
export const CLAWHUB_LOGO_URL =
+1 -1
View File
@@ -83,6 +83,6 @@ BlockedVersionEmail.PreviewProps = {
version: "1.2.3",
findingSummary: "Attempts to exfiltrate credentials.",
validateCommand: "clawhub scan download @scope/demo-plugin --version 1.2.3 --kind plugin",
docsUrl: "https://docs.clawhub.ai/moderation",
docsUrl: "https://docs.openclaw.ai/clawhub/moderation",
preheader: "ClawHub blocked @scope/demo-plugin@1.2.3 after a security scan.",
} satisfies BlockedVersionEmailProps;
+1 -1
View File
@@ -38,7 +38,7 @@ describe("React Email templates", () => {
expect(html).toContain("ClawHub");
expect(html).toContain("OpenClaw");
expect(html).toContain("https://docs.clawhub.ai");
expect(html).toContain("https://docs.openclaw.ai/clawhub");
expect(text).toContain("ClawHub");
});
});
+1 -1
View File
@@ -81,7 +81,7 @@ PluginInspectorFindingsEmail.PreviewProps = {
meta: "legacy-before-agent-start · deprecation-warning · P2",
message: "legacy before_agent_start hook is deprecated",
fix: "Replace the legacy before_agent_start hook with current prompt hooks.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
],
} satisfies PluginInspectorFindingsEmailProps;
+1 -4
View File
@@ -9,7 +9,7 @@
"type": "module",
"scripts": {
"admin": "bun packages/clawhub-admin/src/cli.ts",
"build": "bun run docs:build && vite build && bun scripts/copy-og-assets.ts",
"build": "vite build && bun scripts/copy-og-assets.ts",
"check": "bun run lint",
"check:peers": "bun scripts/check-peer-deps.ts",
"check:secrets": "bun scripts/check-staged-secrets.mjs",
@@ -38,10 +38,8 @@
"dev": "bun --bun vite dev --port 3000",
"dev:workers": "bun scripts/dev-workers.ts",
"dev:worktree": "wt --yes dev",
"docs:build": "node scripts/docs-builder.mjs",
"docs:list": "bun scripts/docs-list.ts",
"docs:run": "bun scripts/docs-run.ts",
"docs:smoke": "node scripts/docs-smoke.mjs",
"email:dev": "email dev --dir emails --port 8765",
"eval:clawscan:security-signals": "bun scripts/eval/clawscan-security-signals.ts",
"format": "oxfmt --write",
@@ -145,7 +143,6 @@
"jsdom": "29.1.1",
"nitro": "3.0.260429-beta",
"only-allow": "1.2.2",
"openclaw-docs-site": "github:openclaw/docs#8e2588e6d718a3c25499688bbb9ad9f0ccba3677",
"oxfmt": "0.54.0",
"oxlint": "1.69.0",
"oxlint-tsgolint": "0.23.0",
@@ -209,36 +209,7 @@ function artifactIdentity(bytes: Uint8Array) {
}
afterEach(() => {
httpMocks.apiRequest.mockReset();
httpMocks.apiRequestForm.mockReset();
httpMocks.downloadZip.mockReset();
httpMocks.fetchBinary.mockReset();
httpMocks.fetchText.mockReset();
httpMocks.uploadBinary.mockReset();
httpMocks.registryUrl.mockReset().mockImplementation((path: string, registry: string) => {
const base = registry.endsWith("/") ? registry : `${registry}/`;
const relative = path.startsWith("/") ? path.slice(1) : path;
return new URL(relative, base);
});
authTokenMocks.requireAuthToken.mockReset().mockResolvedValue("tkn");
authTokenMocks.getOptionalAuthToken.mockReset().mockResolvedValue(undefined);
registryMocks.getRegistry.mockReset().mockResolvedValue("https://clawhub.ai");
inspectorMocks.pluginRoot.runCheck.mockReset();
inspectorMocks.reports.renderTextSummary
.mockReset()
.mockImplementation((report: { status?: string }) => {
return `Plugin Inspector: ${report.status}`;
});
inspectorMocks.reports.sanitizeArtifact
.mockReset()
.mockImplementation((report: unknown) => report);
inspectorMocks.ci.writeOutputs.mockReset();
uiMocks.fail.mockClear();
uiMocks.promptConfirm.mockClear();
uiMocks.spinner.stop.mockClear();
uiMocks.spinner.fail.mockClear();
uiMocks.spinner.succeed.mockClear();
uiMocks.spinner.start.mockClear();
vi.clearAllMocks();
mockLog.mockClear();
mockWrite.mockClear();
uiMocks.spinner.text = "";
@@ -386,7 +357,7 @@ describe("package commands", () => {
evidence: ["src/index.ts:4", { hook: "before_agent_start" }],
authorRemediation: {
summary: "Move the hook to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
docsUrl: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-hook",
},
},
{
@@ -414,7 +385,9 @@ describe("package commands", () => {
"WARNING legacy-hook (deprecation-warning) P2: legacy hook is deprecated",
);
expect(output).toContain("Fix: Move the hook to before_prompt_build.");
expect(output).toContain("Docs: https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook");
expect(output).toContain(
"Docs: https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-hook",
);
expect(output).toContain("Evidence:");
expect(output).toContain("- src/index.ts:4");
expect(output).not.toContain("runtime-tool-capture");
@@ -451,7 +424,7 @@ describe("package commands", () => {
message: "legacy hook is deprecated",
authorRemediation: {
summary: "Move the hook to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
docsUrl: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-hook",
},
},
{
@@ -489,7 +462,7 @@ describe("package commands", () => {
message: "legacy hook is deprecated",
authorRemediation: {
summary: "Move the hook to before_prompt_build.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
docsUrl: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-hook",
},
},
{
@@ -499,7 +472,7 @@ describe("package commands", () => {
authorRemediation: {
summary: "Declare the OpenClaw plugin API range this package supports.",
docsUrl:
"https://docs.clawhub.ai/plugin-validation-fixes#package-plugin-api-compat-missing",
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#package-plugin-api-compat-missing",
},
},
],
@@ -2488,7 +2461,8 @@ describe("package commands", () => {
message: "legacy before_agent_start hook is deprecated",
authorRemediation: {
summary: "Replace the legacy before_agent_start hook with current prompt hooks.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
},
],
@@ -2510,7 +2484,7 @@ describe("package commands", () => {
" Fix: Replace the legacy before_agent_start hook with current prompt hooks.",
);
expect(mockLog).toHaveBeenCalledWith(
" Docs: https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
" Docs: https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
);
} finally {
await rm(workdir, { recursive: true, force: true });
@@ -67,7 +67,7 @@ const LEGACY_DOT_DIR = ".clawdhub";
const DOT_IGNORE = ".clawhubignore";
const LEGACY_DOT_IGNORE = ".clawdhubignore";
const PACKAGE_PUBLISH_RETRY_COUNT = 5;
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.clawhub.ai/plugin-validation-fixes";
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.openclaw.ai/clawhub/plugin-validation-fixes";
const LEGACY_AUTHOR_REMEDIATION_SUMMARIES = {
"channel-env-vars":
"Move legacy channel environment variable metadata into the current setup/config metadata.",
-30
View File
@@ -1,30 +0,0 @@
# ClawHub
Public documentation for finding, installing, publishing, and operating ClawHub skills and plugins.
> Use this file as a lightweight map of the ClawHub documentation. Fetch individual pages as Markdown with `.md` URLs or `Accept: text/markdown`.
## Agent Resources
- [Markdown page export](https://docs.clawhub.ai/acceptable-usage.md): Append `.md` to any docs page URL for clean Markdown.
- [Sitemap](https://docs.clawhub.ai/sitemap.xml): Search crawler URL index.
- [Robots policy](https://docs.clawhub.ai/robots.txt): Bot and crawler policy.
## Documentation Index
- [Acceptable Usage](https://docs.clawhub.ai/acceptable-usage): Marketplace policy: what ClawHub allows and what it will not host.
- [API v1](https://docs.clawhub.ai/api): Public REST API (v1) overview and conventions.
- [Auth](https://docs.clawhub.ai/auth): ClawHub sign-in, API tokens, CLI login, token storage, and revocation.
- [CLI](https://docs.clawhub.ai/cli): CLI reference: commands, flags, config, lockfile, and sync behavior.
- [How ClawHub Works](https://docs.clawhub.ai/how-it-works): How ClawHub listings, versions, installs, publishing, scans, and updates work.
- [HTTP API](https://docs.clawhub.ai/http-api): HTTP API reference (public + CLI endpoints + auth).
- [ClawHub](https://docs.clawhub.ai/): Public ClawHub overview for discovery, install, publish, security, and the clawhub CLI.
- [Moderation and Account Safety](https://docs.clawhub.ai/moderation): How ClawHub reports, moderation holds, hidden listings, bans, and account standing work.
- [Plugin validation fixes](https://docs.clawhub.ai/plugin-validation-fixes): Fix ClawHub plugin package validation findings before publishing
- [Publishing](https://docs.clawhub.ai/publishing): How ClawHub publishing works for skills, plugins, owners, scopes, releases, and review.
- [Quickstart](https://docs.clawhub.ai/quickstart): Start using ClawHub: find, install, update, and publish skills or plugins.
- [Security](https://docs.clawhub.ai/security): How to report ClawHub security issues and when vulnerabilities are publicly disclosed.
- [Security Audits](https://docs.clawhub.ai/security-audits): How to understand ClawHub security audit results before installing a skill or plugin.
- [Skill format](https://docs.clawhub.ai/skill-format): Skill folder format, required files, allowed file types, limits.
- [Telemetry](https://docs.clawhub.ai/telemetry): Install telemetry collected by the ClawHub CLI and how to opt out.
- [Troubleshooting](https://docs.clawhub.ai/troubleshooting): Troubleshooting ClawHub sign-in, install, publish, sync, update, and API issues.
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
-327
View File
@@ -1,327 +0,0 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptPath = fileURLToPath(import.meta.url);
const repoRoot = path.resolve(path.dirname(scriptPath), "..");
const defaultStageRoot = path.join(repoRoot, ".cache", "openclaw-docs-clawhub");
const defaultOutputDir = path.join(repoRoot, "public", "docs");
const require = createRequire(import.meta.url);
export function planDocsStage(sourceRels) {
return sourceRels
.map((sourceRel) => normalizeRel(sourceRel))
.filter(Boolean)
.filter((sourceRel) => !shouldIgnoreSourceDoc(sourceRel))
.map((sourceRel) => ({
sourceRel,
stageRel: stageRelFor(sourceRel),
injectSourcePath: sourcePathFor(sourceRel),
}))
.sort((a, b) => a.stageRel.localeCompare(b.stageRel));
}
export function docsBasePathForEnv(env = process.env) {
return env.VERCEL_ENV === "preview" ? "/docs" : "";
}
export function prepareDocsMarkdown(markdown, sourcePath) {
const absoluteOpenClawLinks = markdown.replaceAll(
"](/plugins/",
"](https://docs.openclaw.ai/plugins/",
);
return injectSourcePath(absoluteOpenClawLinks, sourcePath);
}
export function resolveOpenClawDocsRepo({
env = process.env,
cwd = repoRoot,
exists = fs.existsSync,
resolvePackageRoot = resolveInstalledOpenClawDocsRepo,
} = {}) {
const explicit = env.OPENCLAW_DOCS_REPO_DIR?.trim();
if (explicit) {
const docsRepoDir = path.resolve(cwd, explicit);
if (isOpenClawDocsRepo(docsRepoDir, exists)) return docsRepoDir;
throw new Error(`${docsRepoDir} is not an openclaw/docs checkout`);
}
const installed = resolvePackageRoot();
if (installed && isOpenClawDocsRepo(installed, exists)) return installed;
throw new Error(
"ClawHub docs need openclaw/docs. Install the openclaw-docs-site package or set OPENCLAW_DOCS_REPO_DIR to a local openclaw/docs checkout.",
);
}
export function buildDocs({
cwd = repoRoot,
docsRepoDir = resolveOpenClawDocsRepo({ cwd }),
outputDir = defaultOutputDir,
stageRoot = defaultStageRoot,
commandRunner = runCommand,
env = process.env,
} = {}) {
ensureOpenClawDocsRepo(docsRepoDir);
ensureBuilderDependencies(docsRepoDir);
prepareStage({ docsRepoDir, stageRoot, cwd });
runBuilderPipeline({ docsRepoDir, stageRoot, cwd, commandRunner, env });
copyGeneratedDocs({ stageRoot, outputDir });
console.log(`ClawHub docs built with openclaw/docs: ${path.relative(cwd, outputDir)}`);
}
export function ensureOpenClawDocsRepo(docsRepoDir) {
if (!isOpenClawDocsRepo(docsRepoDir, fs.existsSync)) {
throw new Error(`${docsRepoDir} is not an openclaw/docs checkout`);
}
}
function prepareStage({ docsRepoDir, stageRoot, cwd }) {
fs.rmSync(stageRoot, { recursive: true, force: true });
fs.mkdirSync(path.join(stageRoot, "docs"), { recursive: true });
fs.mkdirSync(path.join(stageRoot, "scripts"), { recursive: true });
fs.mkdirSync(path.join(stageRoot, ".openclaw-sync"), { recursive: true });
symlinkOrCopy(
path.join(docsRepoDir, "scripts", "docs-site"),
path.join(stageRoot, "scripts", "docs-site"),
);
const nodeModulesDir = resolveBuilderNodeModules(docsRepoDir);
symlinkOrCopy(nodeModulesDir, path.join(stageRoot, "node_modules"));
const docsSourceDir = path.join(cwd, "docs");
const entries = planDocsStage(listFiles(docsSourceDir));
for (const entry of entries) {
const source = path.join(docsSourceDir, entry.sourceRel);
const target = path.join(stageRoot, "docs", entry.stageRel);
fs.mkdirSync(path.dirname(target), { recursive: true });
if (entry.injectSourcePath) {
fs.writeFileSync(
target,
prepareDocsMarkdown(fs.readFileSync(source, "utf8"), entry.injectSourcePath),
"utf8",
);
} else {
fs.copyFileSync(source, target);
}
}
fs.writeFileSync(
path.join(stageRoot, ".openclaw-sync", "source.json"),
`${JSON.stringify(sourceMetadata(cwd), null, 2)}\n`,
"utf8",
);
}
function runBuilderPipeline({ docsRepoDir, stageRoot, cwd, commandRunner, env }) {
const sourceRepoDir = env.DOCS_SOURCE_REPO_DIR?.trim() || cwd;
const builderEnv = {
...env,
DOCS_SITE_BASE_PATH: docsBasePathForEnv(env),
DOCS_SITE_LEGACY_BASE_PATH: "",
DOCS_SITE_CANONICAL_ORIGIN: "https://docs.clawhub.ai",
DOCS_SITE_CHAT_AUTH_URL: "https://clawhub.ai/auth/docs",
DOCS_FEEDBACK_ISSUE_REPO: "openclaw/clawhub",
DOCS_SOURCE_REPO_DIR: sourceRepoDir,
DOCS_SOURCE_REPO_URL: "https://github.com/openclaw/clawhub",
};
const nodeScripts = [
"build.mjs",
"search-index.mjs",
"source-index.mjs",
"pagefind-normalize.mjs",
];
for (const script of nodeScripts.slice(0, 3)) {
commandRunner("node", [path.join(docsRepoDir, "scripts", "docs-site", script)], {
cwd: stageRoot,
env: builderEnv,
});
}
commandRunner(
resolveBuilderBin(docsRepoDir, pagefindBinName()),
[
"--site",
path.join(stageRoot, "dist", "docs-site"),
"--output-path",
path.join(stageRoot, "dist", "docs-site", "pagefind"),
],
{ cwd: stageRoot, env: builderEnv },
);
commandRunner(
"node",
[path.join(docsRepoDir, "scripts", "docs-site", "pagefind-normalize.mjs")],
{
cwd: stageRoot,
env: builderEnv,
},
);
}
function ensureBuilderDependencies(docsRepoDir) {
resolveBuilderBin(docsRepoDir, pagefindBinName());
}
function copyGeneratedDocs({ stageRoot, outputDir }) {
const built = path.join(stageRoot, "dist", "docs-site");
if (!fs.existsSync(path.join(built, "index.html"))) {
throw new Error(
`openclaw/docs did not produce ${path.relative(repoRoot, path.join(built, "index.html"))}`,
);
}
fs.rmSync(outputDir, { recursive: true, force: true });
fs.mkdirSync(path.dirname(outputDir), { recursive: true });
fs.cpSync(built, outputDir, { recursive: true });
const llmsDiscoverySource = path.join(built, ".well-known", "llms.txt");
const llmsDiscoveryTarget = path.join(path.dirname(outputDir), ".well-known", "llms.txt");
fs.mkdirSync(path.dirname(llmsDiscoveryTarget), { recursive: true });
fs.copyFileSync(llmsDiscoverySource, llmsDiscoveryTarget);
}
function shouldIgnoreSourceDoc(sourceRel) {
return (
sourceRel === "README.md" ||
sourceRel === "AGENTS.md" ||
sourceRel.startsWith("specs/") ||
sourceRel.startsWith(".")
);
}
function stageRelFor(sourceRel) {
if (sourceRel === "clawhub.md") return "index.md";
return sourceRel;
}
function sourcePathFor(sourceRel) {
if (!/\.(md|mdx)$/u.test(sourceRel)) return null;
if (sourceRel === "clawhub.md") return "clawhub/index.md";
return `clawhub/${sourceRel}`;
}
function injectSourcePath(markdown, sourcePath) {
const sourceYaml = `x-i18n:\n source_path: ${JSON.stringify(sourcePath)}\n`;
if (markdown.startsWith("---\n")) return markdown.replace("---\n", `---\n${sourceYaml}`);
return `---\n${sourceYaml}---\n\n${markdown}`;
}
function sourceMetadata(cwd) {
const sha = git(["rev-parse", "HEAD"], cwd) || null;
return {
repository: "openclaw/clawhub",
sha,
sources: {
clawhub: {
repository: "openclaw/clawhub",
sha,
},
},
};
}
function git(args, cwd) {
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
return result.status === 0 ? result.stdout.trim() : "";
}
function listFiles(dir) {
const files = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
for (const child of listFiles(full)) files.push(path.join(entry.name, child));
} else if (entry.isFile()) {
files.push(entry.name);
}
}
return files.map((file) => normalizeRel(file)).sort();
}
function symlinkOrCopy(source, target) {
fs.rmSync(target, { recursive: true, force: true });
try {
fs.symlinkSync(source, target, "junction");
} catch {
fs.cpSync(source, target, { recursive: true });
}
}
function resolveInstalledOpenClawDocsRepo() {
try {
return path.dirname(require.resolve("openclaw-docs-site/package.json"));
} catch {
return "";
}
}
function resolveBuilderNodeModules(docsRepoDir) {
const candidates = [
path.join(docsRepoDir, "node_modules"),
path.join(path.dirname(docsRepoDir), "node_modules"),
path.join(repoRoot, "node_modules"),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) return candidate;
}
throw new Error(
`Could not find node_modules for openclaw/docs at ${docsRepoDir}. Run bun install in ClawHub or install dependencies in the local openclaw/docs checkout.`,
);
}
function resolveBuilderBin(docsRepoDir, name) {
const candidates = [
path.join(docsRepoDir, "node_modules", ".bin", name),
path.join(path.dirname(docsRepoDir), ".bin", name),
path.join(path.dirname(docsRepoDir), "node_modules", ".bin", name),
path.join(repoRoot, "node_modules", ".bin", name),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) return candidate;
}
throw new Error(
`Could not find ${name} for openclaw/docs at ${docsRepoDir}. Run bun install in ClawHub or install dependencies in the local openclaw/docs checkout.`,
);
}
function isOpenClawDocsRepo(candidate, exists) {
if (!exists(path.join(candidate, "package.json"))) return false;
if (!exists(path.join(candidate, "scripts", "docs-site", "build.mjs"))) return false;
try {
const pkg = JSON.parse(fs.readFileSync(path.join(candidate, "package.json"), "utf8"));
return pkg.name === "openclaw-docs-site";
} catch {
return false;
}
}
function normalizeRel(value) {
const rel = String(value).replaceAll("\\", "/").replace(/^\/+/, "");
const normalized = path.posix.normalize(rel);
return normalized === "." || normalized.startsWith("../") ? "" : normalized;
}
function pagefindBinName() {
return process.platform === "win32" ? "pagefind.cmd" : "pagefind";
}
function runCommand(command, args, options) {
const result = spawnSync(command, args, {
cwd: options.cwd,
env: options.env,
stdio: "inherit",
});
if (result.error) throw result.error;
if (result.status !== 0)
throw new Error(`${command} ${args.join(" ")} failed with ${result.status}`);
}
if (process.argv[1] === scriptPath) {
buildDocs();
}
-120
View File
@@ -1,120 +0,0 @@
/* @vitest-environment node */
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
docsBasePathForEnv,
ensureOpenClawDocsRepo,
planDocsStage,
prepareDocsMarkdown,
resolveOpenClawDocsRepo,
} from "./docs-builder.mjs";
describe("docs-builder", () => {
it("stages ClawHub docs in the shape expected by openclaw/docs", () => {
const entries = planDocsStage([
"README.md",
"assets/clawd-logo.png",
"clawhub.md",
"docs.json",
"quickstart.md",
"specs/private.md",
]);
expect(entries).toEqual([
{
injectSourcePath: null,
sourceRel: "assets/clawd-logo.png",
stageRel: "assets/clawd-logo.png",
},
{ injectSourcePath: null, sourceRel: "docs.json", stageRel: "docs.json" },
{
injectSourcePath: "clawhub/index.md",
sourceRel: "clawhub.md",
stageRel: "index.md",
},
{
injectSourcePath: "clawhub/quickstart.md",
sourceRel: "quickstart.md",
stageRel: "quickstart.md",
},
]);
});
it("uses root links in production and /docs links on Vercel previews", () => {
expect(docsBasePathForEnv({ VERCEL_ENV: "production" })).toBe("");
expect(docsBasePathForEnv({ VERCEL_ENV: "preview" })).toBe("/docs");
expect(docsBasePathForEnv({})).toBe("");
});
it("makes OpenClaw plugin guide links absolute while preserving ClawHub source metadata", () => {
expect(
prepareDocsMarkdown(
"See [Plugin manifest](/plugins/manifest) and [Publishing](/publishing).\n",
"clawhub/plugin-validation-fixes.md",
),
).toContain(
"[Plugin manifest](https://docs.openclaw.ai/plugins/manifest) and [Publishing](/publishing)",
);
});
it("resolves an explicit openclaw/docs checkout", () => {
const docsRepoDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawhub-openclaw-docs-"));
fs.mkdirSync(path.join(docsRepoDir, "scripts", "docs-site"), { recursive: true });
fs.writeFileSync(
path.join(docsRepoDir, "package.json"),
`${JSON.stringify({ name: "openclaw-docs-site" })}\n`,
"utf8",
);
fs.writeFileSync(path.join(docsRepoDir, "scripts", "docs-site", "build.mjs"), "", "utf8");
expect(
resolveOpenClawDocsRepo({
cwd: "/repo/clawhub",
env: { OPENCLAW_DOCS_REPO_DIR: docsRepoDir },
homedir: "/home/me",
}),
).toBe(docsRepoDir);
});
it("resolves the installed openclaw-docs-site package when no checkout is explicit", () => {
const docsRepoDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawhub-openclaw-docs-"));
fs.mkdirSync(path.join(docsRepoDir, "scripts", "docs-site"), { recursive: true });
fs.writeFileSync(
path.join(docsRepoDir, "package.json"),
`${JSON.stringify({ name: "openclaw-docs-site" })}\n`,
"utf8",
);
fs.writeFileSync(path.join(docsRepoDir, "scripts", "docs-site", "build.mjs"), "", "utf8");
expect(
resolveOpenClawDocsRepo({
cwd: "/repo/clawhub",
env: {},
homedir: "/home/me",
resolvePackageRoot: () => docsRepoDir,
}),
).toBe(docsRepoDir);
});
it("fails when neither an explicit checkout nor installed package is available", () => {
expect(() =>
resolveOpenClawDocsRepo({
cwd: "/repo/clawhub",
env: {},
exists: () => false,
homedir: "/home/me",
resolvePackageRoot: () => "",
}),
).toThrow(/OPENCLAW_DOCS_REPO_DIR/);
});
it("rejects invalid openclaw/docs roots instead of cloning one", () => {
const invalidDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawhub-not-docs-"));
fs.writeFileSync(path.join(invalidDir, "package.json"), "{}\n", "utf8");
expect(() => ensureOpenClawDocsRepo(invalidDir)).toThrow(/not an openclaw\/docs checkout/);
});
});
+76 -79
View File
@@ -1,94 +1,91 @@
#!/usr/bin/env bun
import { spawnSync } from "node:child_process";
import { createReadStream, existsSync, statSync } from "node:fs";
import { createServer } from "node:http";
import { extname, join, normalize, resolve, sep } from "node:path";
import { existsSync, mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const HERE = resolve(fileURLToPath(import.meta.url), "..");
const HERE = dirname(fileURLToPath(import.meta.url));
const CLAWHUB_ROOT = resolve(HERE, "..");
const DOCS_ROOT = resolve(CLAWHUB_ROOT, "public", "docs");
const HOST = process.env.DOCS_RUN_HOST || "127.0.0.1";
const PORT = Number(process.env.DOCS_RUN_PORT || "4174");
const OPENCLAW_REPO_PATH = process.env.OPENCLAW_REPO_PATH
? resolve(process.env.OPENCLAW_REPO_PATH)
: resolve(CLAWHUB_ROOT, "..", "openclaw");
const PREVIEW_ROOT = resolve(CLAWHUB_ROOT, ".cache", "openclaw-docs-preview");
run("node", ["scripts/docs-builder.mjs"]);
const server = createServer((request, response) => {
if (!request.url) {
response.writeHead(400);
response.end("Bad request");
return;
}
const url = new URL(request.url, `http://${HOST}:${PORT}`);
const file = resolveDocsPath(url.pathname);
if (!file) {
response.writeHead(404);
response.end("Not found");
return;
}
response.writeHead(200, { "Content-Type": contentType(file) });
createReadStream(file).pipe(response);
});
server.listen(PORT, HOST, () => {
console.log(`ClawHub docs preview: http://${HOST}:${PORT}/`);
});
function run(command: string, args: string[]) {
function run(command: string, args: string[], options: { cwd?: string } = {}) {
const result = spawnSync(command, args, {
cwd: CLAWHUB_ROOT,
cwd: options.cwd ?? CLAWHUB_ROOT,
env: process.env,
stdio: "inherit",
});
if (result.error) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
}
function resolveDocsPath(pathname: string) {
const decoded = decodeURIComponent(pathname);
const normalized = normalize(decoded).replace(/^(\.\.(?:\/|\\|$))+/, "");
let candidate = resolve(DOCS_ROOT, `.${sep}${normalized}`);
if (!candidate.startsWith(`${DOCS_ROOT}${sep}`) && candidate !== DOCS_ROOT) {
return "";
if (result.error) {
throw result.error;
}
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
candidate = join(candidate, "index.html");
} else if (!extname(candidate) && existsSync(join(candidate, "index.html"))) {
candidate = join(candidate, "index.html");
}
return existsSync(candidate) && statSync(candidate).isFile() ? candidate : "";
}
function contentType(file: string) {
switch (extname(file)) {
case ".css":
return "text/css; charset=utf-8";
case ".gif":
return "image/gif";
case ".html":
return "text/html; charset=utf-8";
case ".js":
case ".mjs":
return "text/javascript; charset=utf-8";
case ".json":
return "application/json; charset=utf-8";
case ".md":
case ".txt":
return "text/plain; charset=utf-8";
case ".png":
return "image/png";
case ".svg":
return "image/svg+xml";
case ".wasm":
return "application/wasm";
default:
return "application/octet-stream";
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
function capture(command: string, args: string[], cwd: string) {
const result = spawnSync(command, args, {
cwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "inherit"],
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
return result.stdout.trim();
}
function assertOpenClawRepo(path: string) {
const syncScript = resolve(path, "scripts", "docs-sync-publish.mjs");
if (!existsSync(syncScript)) {
console.error(
[
`OpenClaw docs sync script was not found at ${syncScript}.`,
"",
"Set OPENCLAW_REPO_PATH to your OpenClaw checkout, for example:",
" OPENCLAW_REPO_PATH=/path/to/openclaw bun run docs:run",
].join("\n"),
);
process.exit(1);
}
return syncScript;
}
const syncScript = assertOpenClawRepo(OPENCLAW_REPO_PATH);
mkdirSync(PREVIEW_ROOT, { recursive: true });
const openClawSha = capture("git", ["rev-parse", "HEAD"], OPENCLAW_REPO_PATH);
const clawHubSha = capture("git", ["rev-parse", "HEAD"], CLAWHUB_ROOT);
console.log(`Syncing ClawHub docs into OpenClaw docs preview`);
console.log(` OpenClaw: ${OPENCLAW_REPO_PATH}`);
console.log(` ClawHub: ${CLAWHUB_ROOT}`);
console.log(` Preview: ${PREVIEW_ROOT}`);
run("node", [
syncScript,
"--target",
PREVIEW_ROOT,
"--source-repo",
"openclaw/openclaw",
"--source-sha",
openClawSha,
"--clawhub-repo",
CLAWHUB_ROOT,
"--clawhub-source-repo",
"openclaw/clawhub",
"--clawhub-source-sha",
clawHubSha,
]);
console.log("");
console.log("Starting Mintlify docs preview. Open the printed local URL, then go to /clawhub.");
run("mint", ["dev"], { cwd: resolve(PREVIEW_ROOT, "docs") });
-174
View File
@@ -1,174 +0,0 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptPath = fileURLToPath(import.meta.url);
const repoRoot = path.resolve(path.dirname(scriptPath), "..");
const site = path.join(repoRoot, "public", "docs");
const index = read("index.html");
const quickstart = read("quickstart/index.html");
const siteJs = read("assets/docs-site.js");
const cli = read("cli/index.html");
const pluginValidationFixes = read("plugin-validation-fixes/index.html");
const llms = read("llms.txt");
const llmsDiscovery = fs.readFileSync(
path.join(repoRoot, "public", ".well-known", "llms.txt"),
"utf8",
);
const robots = read("robots.txt");
const searchIndex = JSON.parse(read("docs-search.json"));
const sourceIndexMeta = JSON.parse(read("source-index-meta.json"));
const vercelConfig = JSON.parse(fs.readFileSync(path.join(repoRoot, "vercel.json"), "utf8"));
assert(
index.includes("<title>ClawHub - ClawHub</title>"),
"index should render ClawHub as the docs home",
);
assert(
index.includes('href="https://docs.clawhub.ai/"'),
"index should use docs.clawhub.ai canonical URL",
);
assert(index.includes('href="/quickstart"'), "index should link to the quickstart child route");
assert(
!fs.existsSync(path.join(site, "clawhub", "index.html")),
"home page should not publish /docs/clawhub",
);
assert(
!fs.existsSync(path.join(site, "README", "index.html")),
"README.md should not publish as a docs page",
);
assert(!fs.existsSync(path.join(site, "specs")), "specs should not publish under /docs");
assert(fs.existsSync(path.join(site, "assets", "clawd-logo.png")), "docs assets should be copied");
assert(quickstart.includes("Quickstart"), "quickstart child route should render");
assert(index.includes("Ask Molty"), "Ask Molty widget should render");
assert(index.includes("ClawHub docs assistant"), "Ask Molty should use the ClawHub chat label");
assert(
siteJs.includes('new URL("https://clawhub.ai/auth/docs",location.href)'),
"Ask Molty auth should use canonical ClawHub auth",
);
assert(
!siteJs.includes("https://hub.openclaw.ai/docs/auth"),
"Ask Molty auth should not use OpenClaw hub",
);
assert(
index.includes("Search ClawHub docs..."),
"search placeholder should come from ClawHub docs config",
);
assert(
index.includes("Publish a package"),
"search suggestions should come from ClawHub docs config",
);
assert(
index.includes("https://github.com/openclaw/clawhub/edit/main/docs/clawhub.md"),
"home page edit link should point back to ClawHub source docs",
);
assert(
!index.includes("https://github.com/openclaw/openclaw"),
"ClawHub docs should not render OpenClaw repo links in the header or edit links",
);
assert(
llms.includes("ClawHub documentation") && !llms.includes("OpenClaw documentation"),
"llms.txt should describe the ClawHub docs corpus",
);
assert(
/https:\/\/docs\.clawhub\.ai\/[^)\s]+\.md/u.test(llms) &&
!llms.includes("/start/getting-started.md"),
"llms.txt should advertise a real ClawHub Markdown page",
);
assert(llmsDiscovery === llms, "root .well-known LLMS discovery should match the docs artifact");
assert(
robots.includes("# ClawHub documentation crawler policy") &&
robots.includes("Sitemap: https://docs.clawhub.ai/sitemap.xml") &&
!robots.includes("OpenClaw documentation crawler policy"),
"robots.txt should use ClawHub docs metadata",
);
assert(
pluginValidationFixes.includes('href="/plugin-validation-fixes#package-json-missing"') &&
!pluginValidationFixes.includes('href="./plugin-validation-fixes.md#package-json-missing"'),
"relative Markdown links should resolve to docs routes",
);
assert(
pluginValidationFixes.includes('href="https://docs.openclaw.ai/plugins/building-plugins"') &&
!pluginValidationFixes.includes('href="/docs/plugins/building-plugins"'),
"OpenClaw plugin guide links should use the canonical OpenClaw docs host",
);
assert(
cli.includes(
"https://github.com/openclaw/clawhub/blob/main/.github/workflows/package-publish.yml",
) && !cli.includes('href="../.github/workflows/package-publish.yml"'),
"repo-relative source links should resolve to GitHub",
);
assert(
searchIndex.entries.some((entry) => entry.url === "/" && entry.title === "ClawHub"),
"search index should include the docs home page",
);
assert(
sourceIndexMeta.repository === "openclaw/clawhub",
"source index should identify openclaw/clawhub",
);
assert(
vercelConfig.redirects?.some(
(route) =>
route.source === "/docs/:path*" &&
route.destination === "https://docs.clawhub.ai/:path*" &&
route.statusCode === 308,
),
"legacy ClawHub docs paths should redirect to the canonical docs host",
);
assert(
vercelConfig.rewrites?.some(
(route) =>
route.source === "/" &&
route.destination === "/docs" &&
route.has?.some(
(condition) =>
condition.type === "header" &&
condition.key === "host" &&
condition.value === "docs.clawhub.ai",
),
),
"docs.clawhub.ai root should serve the generated docs artifact",
);
assert(
vercelConfig.redirects?.some(
(route) =>
route.source === "/docs/:path*" &&
route.destination === "https://docs.clawhub.ai/:path*" &&
route.statusCode === 308 &&
route.has?.some(
(condition) =>
condition.type === "header" &&
condition.key === "host" &&
condition.value === "docs.clawhub.ai",
),
),
"docs.clawhub.ai/docs paths should redirect to root docs paths",
);
assert(
vercelConfig.rewrites?.some(
(route) =>
route.source === "/:path*" &&
route.destination === "/docs/:path*" &&
route.has?.some(
(condition) =>
condition.type === "header" &&
condition.key === "host" &&
condition.value === "docs.clawhub.ai",
),
),
"docs.clawhub.ai should serve the generated docs artifact",
);
console.log("ClawHub docs smoke ok");
function read(rel) {
const file = path.join(site, rel);
if (!fs.existsSync(file)) throw new Error(`${rel} does not exist; run bun run docs:build first`);
return fs.readFileSync(file, "utf8");
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
-97
View File
@@ -1,97 +0,0 @@
/* @vitest-environment node */
import { mockEvent } from "h3";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const handler = (await import("../routes/ask-molty/[...]")).default;
const originalProxyOrigin = process.env.ASK_MOLTY_PROXY_ORIGIN;
const fetchMock = vi.fn(async (_target: URL, _init: RequestInit) => new Response("ok"));
describe("ask-molty proxy route", () => {
beforeEach(() => {
process.env.ASK_MOLTY_PROXY_ORIGIN = "https://docs-chat.example.test";
fetchMock.mockClear();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
if (originalProxyOrigin === undefined) delete process.env.ASK_MOLTY_PROXY_ORIGIN;
else process.env.ASK_MOLTY_PROXY_ORIGIN = originalProxyOrigin;
vi.unstubAllGlobals();
});
it("strips ClawHub credentials before forwarding to the docs assistant origin", async () => {
const event = mockEvent("http://127.0.0.1:3000/ask-molty/api/chat?q=docs", {
headers: {
authorization: "Bearer convex-token",
cookie: "clawhub_session=secret; ask_molty_session=signed.value; unrelated=value",
"x-request-id": "request-123",
},
});
await handler(event);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [target, init] = fetchMock.mock.calls[0] as [string | URL, RequestInit];
expect(String(target)).toBe("https://docs-chat.example.test/ask-molty/api/chat?q=docs");
expect(init.method).toBe("GET");
expect(init.redirect).toBe("manual");
const forwarded = init.headers as Headers;
expect(forwarded.get("x-request-id")).toBe("request-123");
expect(forwarded.has("authorization")).toBe(false);
expect(forwarded.get("cookie")).toBe("ask_molty_session=signed.value");
});
it("preserves the docs assistant sign-in redirect", async () => {
const headers = new Headers({
Location:
"https://hub.openclaw.ai/docs/auth?return_to=https%3A%2F%2Fdocs-chat.openclaw.ai%2F",
});
headers.append("Set-Cookie", "clawhub_session=evil; Domain=clawhub.ai; Path=/");
headers.append(
"Set-Cookie",
"ask_molty_session=signed.value; Max-Age=604800; Domain=clawhub.ai; Path=/; HttpOnly; Secure; SameSite=None",
);
fetchMock.mockResolvedValueOnce(
new Response(null, {
status: 302,
headers,
}),
);
const event = mockEvent(
"https://docs.clawhub.ai/ask-molty/sign-in?return_to=https%3A%2F%2Fdocs.clawhub.ai%2F",
);
const response = await handler(event);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe(
"https://clawhub.ai/auth/docs?return_to=https%3A%2F%2Fdocs.clawhub.ai%2F",
);
expect(response.headers.get("set-cookie")).toBe(
"ask_molty_session=signed.value; Max-Age=604800; Path=/ask-molty; HttpOnly; Secure; SameSite=Lax",
);
});
it("keeps local docs auth on the local ClawHub origin", async () => {
fetchMock.mockResolvedValueOnce(
new Response(null, {
status: 302,
headers: {
Location:
"https://hub.openclaw.ai/docs/auth?return_to=https%3A%2F%2Fdocs-chat.openclaw.ai%2F",
},
}),
);
const event = mockEvent(
"http://127.0.0.1:3000/ask-molty/sign-in?return_to=http%3A%2F%2Flocalhost%3A4173%2Fdocs",
);
const response = await handler(event);
expect(response.headers.get("location")).toBe(
"http://127.0.0.1:3000/auth/docs?return_to=http%3A%2F%2Flocalhost%3A4173%2Fdocs",
);
});
});
-146
View File
@@ -1,146 +0,0 @@
import {
HTTPResponse,
defineEventHandler,
getMethod,
getRequestHeaders,
getRequestURL,
readRawBody,
} from "h3";
import { normalizeDocsReturnTo } from "../../../src/lib/docsAuth";
const DEFAULT_ASK_MOLTY_ORIGIN = "https://docs-chat.openclaw.ai";
const askMoltyCookieName = "ask_molty_session";
const hopByHopHeaders = new Set([
"connection",
"content-length",
"expect",
"host",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
]);
export default defineEventHandler(async (event) => {
const origin = normalizeProxyOrigin(process.env.ASK_MOLTY_PROXY_ORIGIN);
const requestUrl = getRequestURL(event);
const target = new URL(`${requestUrl.pathname}${requestUrl.search}`, origin);
const headers = forwardedHeaders(getRequestHeaders(event));
const method = getMethod(event);
const body = method === "GET" || method === "HEAD" ? undefined : await readRawBody(event);
const response = await fetch(target, {
body,
headers,
method,
redirect: "manual",
});
return new HTTPResponse(response.body, {
status: response.status,
statusText: response.statusText,
headers: forwardedResponseHeaders(response.headers, requestUrl),
});
});
function normalizeProxyOrigin(value?: string) {
try {
return new URL(value || DEFAULT_ASK_MOLTY_ORIGIN).origin;
} catch {
return DEFAULT_ASK_MOLTY_ORIGIN;
}
}
function forwardedHeaders(rawHeaders: HeadersInit) {
const headers = new Headers();
for (const [name, value] of new Headers(rawHeaders).entries()) {
const lowerName = name.toLowerCase();
if (hopByHopHeaders.has(lowerName) || lowerName === "authorization") continue;
if (lowerName === "cookie") {
const askMoltyCookie = pickCookie(value, askMoltyCookieName);
if (askMoltyCookie) headers.set("Cookie", askMoltyCookie);
continue;
}
headers.set(name, value);
}
return headers;
}
function pickCookie(header: string, name: string) {
const prefix = `${name}=`;
return header
.split(";")
.map((part) => part.trim())
.find((part) => part.startsWith(prefix));
}
function forwardedResponseHeaders(rawHeaders: Headers, requestUrl: URL) {
const headers = new Headers();
for (const [name, value] of rawHeaders.entries()) {
const lowerName = name.toLowerCase();
if (
["content-encoding", "content-length", "set-cookie", "transfer-encoding"].includes(lowerName)
)
continue;
headers.append(
name,
lowerName === "location" ? canonicalizeAuthLocation(value, requestUrl) : value,
);
}
for (const value of getSetCookieHeaders(rawHeaders)) {
const setCookie = sanitizeAskMoltySetCookie(value);
if (setCookie) headers.append("Set-Cookie", setCookie);
}
return headers;
}
function getSetCookieHeaders(headers: Headers) {
const withGetSetCookie = headers as Headers & { getSetCookie?: () => string[] };
if (typeof withGetSetCookie.getSetCookie === "function") return withGetSetCookie.getSetCookie();
const value = headers.get("set-cookie");
return value ? [value] : [];
}
function canonicalizeAuthLocation(value: string, requestUrl: URL) {
try {
const location = new URL(value);
if (location.origin !== "https://hub.openclaw.ai" || location.pathname !== "/docs/auth") {
return value;
}
const authOrigin = isLocalOrigin(requestUrl.origin) ? requestUrl.origin : "https://clawhub.ai";
const authUrl = new URL(authOrigin);
location.protocol = authUrl.protocol;
location.host = authUrl.host;
location.pathname = "/auth/docs";
const returnTo = normalizeDocsReturnTo(requestUrl.searchParams.get("return_to"), {
currentOrigin: requestUrl.origin,
});
if (returnTo) location.searchParams.set("return_to", returnTo);
return location.href;
} catch {
return value;
}
}
function isLocalOrigin(origin: string) {
try {
return ["localhost", "127.0.0.1"].includes(new URL(origin).hostname);
} catch {
return false;
}
}
function sanitizeAskMoltySetCookie(value: string | null) {
if (!value) return null;
const parts = value.split(";").map((part) => part.trim());
const cookie = parts[0];
if (!cookie?.startsWith(`${askMoltyCookieName}=`)) return null;
const maxAge = parts.find((part) => /^Max-Age=\d+$/i.test(part));
return [cookie, maxAge, "Path=/ask-molty", "HttpOnly", "Secure", "SameSite=Lax"]
.filter(Boolean)
.join("; ");
}
-14
View File
@@ -1,14 +0,0 @@
import { defineEventHandler } from "h3";
const robots = `# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
`;
export default defineEventHandler(() => {
return new Response(robots, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
});
+33 -11
View File
@@ -1,21 +1,43 @@
---
summary: "Superseded Mintlify notes for publishing docs/."
summary: "Mintlify setup notes for publishing docs/."
read_when:
- Setting up docs site
---
# Mintlify
Status: superseded for the CLAW-201 ClawHub docs migration.
Goal: publish `docs/` as a browsable docs site (nice UX for OSS users).
The current migration plan is to build `docs/` with the shared
`openclaw/docs` renderer and serve the generated output from ClawHub under
`https://docs.clawhub.ai`. Do not set up Mintlify for this migration unless a
new issue explicitly reopens that direction.
This repo does **not** include Mintlify config yet (`mint.json` missing).
## Historical note
## Minimal setup
Mintlify was evaluated as a simpler docs host, but the project direction moved
back to reusing the existing `openclaw/docs` framework so ClawHub docs keep the
same shell, Ask Molty integration, search/index generation, and route behavior
as OpenClaw docs without introducing a second docs product.
1. Install Mintlify CLI (per Mintlify docs).
2. Add a `mint.json` at repo root that points to `docs/` pages.
Example (starter):
```json
{
"name": "ClawHub",
"logo": "public/logo.svg",
"navigation": [
{ "group": "Start", "pages": ["docs/README", "docs/quickstart"] },
{ "group": "Concepts", "pages": ["docs/architecture", "docs/skill-format", "docs/telemetry"] },
{ "group": "Reference", "pages": ["docs/cli", "docs/http-api", "docs/auth", "docs/deploy"] }
]
}
```
Notes:
- Mintlify usually wants page paths without extension; keep files as `.md`.
- If you prefer Mintlify conventions, rename to `.mdx` later (optional).
## Recommended “docs UX” additions
- Add an “Overview” page (use `docs/README.md`).
- Keep “Quickstart” copy/paste friendly.
- Provide CLI + HTTP API reference pages (done here).
- Add a Troubleshooting page for common setup failures.
+1 -1
View File
@@ -21,7 +21,7 @@ Canonical product and registry material now lives in `docs/clawhub.md`:
## Summarize or link from OpenClaw docs
These OpenClaw docs should eventually point to `https://docs.clawhub.ai` rather than
These OpenClaw docs should eventually point to the ClawHub docs tab rather than
restate the whole registry guide:
- `docs/tools/clawhub.md`: keep as a concise bridge to ClawHub docs plus OpenClaw-native install examples.
+20 -60
View File
@@ -130,24 +130,8 @@ vi.mock("../lib/useUnifiedSearch", () => ({
vi.mock("../components/ui/dropdown-menu", () => ({
DropdownMenu: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DropdownMenuContent: ({ children, className }: { children: ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
DropdownMenuItem: ({
children,
className,
onClick,
...props
}: {
children: ReactNode;
className?: string;
onClick?: () => void;
"data-status"?: string;
}) => (
<div className={className} data-status={props["data-status"]} onClick={onClick}>
{children}
</div>
),
DropdownMenuContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DropdownMenuItem: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DropdownMenuSeparator: () => <hr />,
DropdownMenuTrigger: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
@@ -214,68 +198,43 @@ describe("Header", () => {
signInMock.mockResolvedValue({ signingIn: true });
});
it("renders text-only content links in the top navbar", () => {
it("renders restored desktop nav rows and segmented theme controls", () => {
setModeMock.mockClear();
render(<Header />);
const topNav = screen.getByRole("navigation", { name: "Primary navigation" });
expect(document.querySelector(".navbar-top-links")).toBeTruthy();
expect(document.querySelector(".navbar-tabs")).toBeNull();
expect(document.querySelector(".theme-mode-toggle")).toBeNull();
expect(within(topNav).getByText("Skills").closest("a")?.querySelector("svg")).toBeNull();
expect(within(topNav).getByText("Plugins").closest("a")?.querySelector("svg")).toBeNull();
expect(within(topNav).getByText("Docs").closest("a")?.getAttribute("href")).toBe(
"https://docs.clawhub.ai/",
);
expect(document.querySelector(".navbar-tabs")).toBeTruthy();
expect(document.querySelector(".navbar-tabs-secondary")).toBeTruthy();
expect(document.querySelector(".theme-mode-toggle")).toBeTruthy();
expect(screen.getByLabelText("Theme mode").className).toContain("theme-mode-toggle");
expect(screen.getByRole("button", { name: /Cycle theme mode/i })).toBeTruthy();
expect(screen.getByRole("button", { name: "System theme" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Light theme" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Dark theme" })).toBeTruthy();
expect(screen.getAllByText("Skills")).toHaveLength(1);
expect(screen.getAllByText("Plugins")).toHaveLength(1);
expect(screen.queryByText("Publishers")).toBeNull();
expect(screen.getAllByText("Publishers")).toHaveLength(1);
expect(screen.getAllByText("Docs")).toHaveLength(1);
expect(screen.queryByText("About")).toBeNull();
expect(screen.queryByText("Dashboard")).toBeNull();
expect(screen.queryByText("Manage")).toBeNull();
expect(screen.getByPlaceholderText("Search skills and plugins")).toBeTruthy();
expect(document.querySelector(".navbar-tabs")?.textContent).toContain("Publishers");
expect(document.querySelector(".navbar-tabs-secondary")?.textContent).toBe("Docs");
fireEvent.click(screen.getByRole("button", { name: /Cycle theme mode/i }));
expect(setModeMock).toHaveBeenCalledWith("light");
fireEvent.click(screen.getByRole("button", { name: "Open menu" }));
expect(screen.getAllByText("Home")).toHaveLength(1);
expect(screen.getAllByText("Skills")).toHaveLength(2);
expect(screen.getAllByText("Plugins")).toHaveLength(2);
expect(screen.queryByText("Publishers")).toBeNull();
expect(screen.getAllByText("Publishers")).toHaveLength(2);
expect(screen.getAllByText("Docs")).toHaveLength(2);
expect(screen.queryByText("About")).toBeNull();
});
it("moves theme mode controls into the signed-in profile menu", () => {
authStatusMock.mockReturnValue({
isAuthenticated: true,
isLoading: false,
me: {
displayName: "Patrick",
email: "patrick@example.com",
handle: "patrick",
image: null,
name: "Patrick",
},
});
render(<Header />);
expect(document.querySelector(".theme-mode-toggle")).toBeNull();
expect(screen.getByText("Theme")).toBeTruthy();
expect(
screen
.getByText("System theme")
.closest(".user-dropdown-theme-item")
?.getAttribute("data-status"),
).toBe("active");
fireEvent.click(screen.getByText("Light theme").closest(".user-dropdown-theme-item")!);
expect(setModeMock).toHaveBeenCalledWith("light");
fireEvent.click(screen.getByText("Dark theme").closest(".user-dropdown-theme-item")!);
expect(setModeMock).toHaveBeenCalledWith("dark");
});
it("renders the GitHub sign-in button with desktop and compact labels", () => {
render(<Header />);
@@ -467,7 +426,8 @@ describe("Header", () => {
.map((element) => element.textContent?.trim())
.filter((label): label is string => Boolean(label));
expect(labels.slice(0, 4)).toEqual(["Home", "Skills", "Plugins", "Docs"]);
expect(labels.slice(0, 4)).toEqual(["Home", "Skills", "Plugins", "Publishers"]);
expect(labels[4]).toBe("Docs");
});
it("links starred skills from the signed-in avatar menu", () => {
+4 -3
View File
@@ -698,7 +698,8 @@ describe("plugin detail route", () => {
scanSource: "nightly",
authorRemediation: {
summary: "Replace the legacy before_agent_start hook with current prompt hooks.",
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
docsUrl:
"https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
},
createdAt: 1,
},
@@ -779,10 +780,10 @@ describe("plugin detail route", () => {
expect(
screen
.getByRole("link", {
name: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
name: "https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start",
})
.getAttribute("href"),
).toBe("https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start");
).toBe("https://docs.openclaw.ai/clawhub/plugin-validation-fixes#legacy-before-agent-start");
expect(screen.getByText("Docs")).toBeTruthy();
expect(screen.getAllByText("OpenClaw 0.9.0").length).toBeGreaterThan(0);
});
+3 -1
View File
@@ -148,7 +148,9 @@ describe("plugins publish route", () => {
renderPublishRoute();
const guideLink = screen.getByRole("link", { name: /Plugin publishing guide/i });
expect(guideLink.getAttribute("href")).toBe("https://docs.clawhub.ai/publishing#plugins");
expect(guideLink.getAttribute("href")).toBe(
"https://docs.openclaw.ai/clawhub/publishing#plugins",
);
expect(guideLink.getAttribute("target")).toBe("_blank");
});
+1 -1
View File
@@ -89,7 +89,7 @@ describe("Upload route", () => {
render(<Upload />);
const guideLink = screen.getByRole("link", { name: /Skill publishing guide/i });
expect(guideLink.getAttribute("href")).toBe("https://docs.clawhub.ai/skill-format");
expect(guideLink.getAttribute("href")).toBe("https://docs.openclaw.ai/clawhub/skill-format");
expect(guideLink.getAttribute("target")).toBe("_blank");
});
+10 -20
View File
@@ -68,31 +68,25 @@ describe("restored UI design contract", () => {
const styles = () => read("src/styles.css");
const theme = () => read("src/lib/theme.ts");
it("requires the single-row header, full-width search, public nav, and profile theme control", () => {
it("requires the restored two-row header, full-width search, public nav, and theme control", () => {
const headerSource = header();
const navSource = navItems();
const css = styles();
expect(headerSource).toContain("Row 1: Brand + Search + Actions");
expect(headerSource).toContain('className="navbar-top"');
expect(headerSource).toContain('className="navbar-top-links"');
expect(headerSource).toContain('className="navbar-search-wrap"');
expect(headerSource).toContain('className="user-dropdown-section-label"');
expect(headerSource).toContain('className="user-dropdown-theme-item"');
expect(headerSource).not.toContain('className="theme-mode-toggle"');
expect(headerSource).toContain('className="theme-mode-toggle"');
expect(headerSource).toContain('className="github-sign-in-button"');
expect(headerSource).toContain('className="sign-in-full-copy"');
expect(headerSource).toContain('className="sign-in-compact-copy"');
expect(headerSource).toContain("Search skills and plugins");
expect(headerSource).not.toContain('className="navbar-tabs-primary"');
expect(headerSource).not.toContain('className="navbar-tabs-secondary"');
expect(headerSource).toContain('className="navbar-tabs-primary"');
expect(headerSource).toContain('className="navbar-tabs-secondary"');
expect(navSource).toContain("export const SECONDARY_NAV_ITEMS");
expect(navSource).not.toContain('label: "Publishers"');
expect(navSource).toContain('label: "Publishers"');
expect(navSource).toContain('label: "Docs"');
expect(navSource).toContain('href: "https://docs.clawhub.ai/"');
expect(navSource).not.toContain('icon: "wrench"');
expect(navSource).not.toContain('icon: "plug"');
expect(navSource).not.toContain('label: "About"');
expect(navSource).not.toContain('label: "Stars"');
expect(navSource).not.toContain('label: "Management"');
@@ -101,20 +95,16 @@ describe("restored UI design contract", () => {
expect(headerShell).toContain("max-width: var(--page-max)");
expect(headerShell).toContain("padding: 0 var(--space-5)");
const topLinks = cssRule(css, ".navbar-top-links");
expect(topLinks).toContain("display: inline-flex");
const topRow = cssRule(css, ".navbar-top");
expect(topRow).toContain(
"grid-template-columns: max-content max-content minmax(220px, 1fr) auto",
);
const themeItem = cssRule(css, ".user-dropdown-theme-item");
expect(themeItem).toContain("min-width: 220px");
const themeControl = cssRule(css, ".theme-mode-toggle");
expect(themeControl).toContain("min-width: 124px");
expect(themeControl).toContain("min-height: 32px");
expect(themeControl).toContain("border: 1px solid var(--line)");
expect(css).toContain("--r-btn: var(--r-sm)");
const compact = cssMediaContaining(css, "(max-width: 760px)", [
"grid-template-columns: 40px minmax(0, 1fr) 40px",
".navbar-search {\n display: flex;",
".navbar-top-links {\n display: none;",
".navbar-tabs {\n display: none;",
".nav-mobile {\n display: inline-flex;",
]);
expect(compact).not.toContain(".navbar-search {\n display: none;");
+123 -50
View File
@@ -19,6 +19,7 @@ import {
routeToBannedAccountPage,
} from "../lib/authErrorMessage";
import { gravatarUrl } from "../lib/gravatar";
import { NAV_ICONS } from "../lib/marketplaceIcons";
import { PRIMARY_NAV_ITEMS, SECONDARY_NAV_ITEMS } from "../lib/nav-items";
import { SITE_NAME } from "../lib/site";
import { applyTheme, useThemeMode } from "../lib/theme";
@@ -45,12 +46,9 @@ import {
SheetHeader,
SheetTitle,
} from "./ui/sheet";
import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group";
const THEME_MODE_ITEMS = [
{ mode: "system", label: "System theme", Icon: Monitor },
{ mode: "light", label: "Light theme", Icon: Sun },
{ mode: "dark", label: "Dark theme", Icon: Moon },
] as const;
const THEME_MODE_SEQUENCE: Array<"system" | "light" | "dark"> = ["system", "light", "dark"];
function GitHubLogo({ className }: { className?: string }) {
return (
@@ -98,6 +96,7 @@ export default function Header() {
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const searchWrapRef = useRef<HTMLDivElement | null>(null);
const ThemeModeIcon = getThemeModeIcon(mode);
const trimmedNavSearchQuery = navSearchQuery.trim();
const showTypeahead = typeaheadOpen && trimmedNavSearchQuery.length > 0;
const {
@@ -164,6 +163,11 @@ export default function Header() {
setMode(next);
};
const cycleThemeMode = () => {
const currentIndex = Math.max(0, THEME_MODE_SEQUENCE.indexOf(mode));
setThemeMode(THEME_MODE_SEQUENCE[(currentIndex + 1) % THEME_MODE_SEQUENCE.length] ?? "system");
};
const handleNavSearch = (e: React.FormEvent) => {
e.preventDefault();
const q = navSearchQuery.trim();
@@ -268,7 +272,9 @@ export default function Header() {
<span className="mobile-nav-brand-name">{SITE_NAME}</span>
</span>
</SheetTitle>
<SheetDescription>Browse sections and access account actions.</SheetDescription>
<SheetDescription>
Browse sections, switch theme, and access account actions.
</SheetDescription>
</SheetHeader>
<div className="mobile-nav-section">
<SheetClose asChild>
@@ -305,6 +311,20 @@ export default function Header() {
</SheetClose>
))}
</div>
<div className="mobile-nav-section">
<div className="mobile-nav-section-title">Theme</div>
<button
className="mobile-nav-link"
type="button"
onClick={() => {
cycleThemeMode();
setMobileMenuOpen(false);
}}
>
<ThemeModeIcon className="h-4 w-4" aria-hidden="true" />
{mode === "system" ? "System theme" : `${mode} theme`}
</button>
</div>
</SheetContent>
</Sheet>
</div>
@@ -320,34 +340,6 @@ export default function Header() {
<span className="brand-name brand-name-responsive">{SITE_NAME}</span>
</Link>
<nav className="navbar-top-links" aria-label="Primary navigation">
{[...PRIMARY_NAV_ITEMS, ...SECONDARY_NAV_ITEMS].map((item) => {
const isActiveByPrefix = item.activePathPrefixes?.some((prefix) =>
location.pathname.startsWith(prefix),
);
return item.href ? (
<a
key={item.href + item.label}
href={item.href}
className="navbar-tab"
data-status={isActiveByPrefix ? "active" : undefined}
>
{item.label}
</a>
) : (
<Link
key={item.to + item.label}
to={item.to}
className="navbar-tab"
search={(item.search ?? {}) as never}
data-status={isActiveByPrefix ? "active" : undefined}
>
{item.label}
</Link>
);
})}
</nav>
<div className="navbar-search-wrap" ref={searchWrapRef}>
<form
className="navbar-search"
@@ -397,6 +389,42 @@ export default function Header() {
>
<Search size={18} aria-hidden="true" />
</button>
<div className="theme-toggle">
<div className="theme-cycle-group" aria-label="Theme controls">
<button
type="button"
className="theme-cycle-button theme-cycle-button-mode"
onClick={cycleThemeMode}
aria-label={`Cycle theme mode. Current: ${mode}`}
title={`Theme mode: ${mode}`}
>
<ThemeModeIcon className="h-4 w-4" aria-hidden="true" />
</button>
</div>
<ToggleGroup
className="theme-mode-toggle"
type="single"
value={mode}
onValueChange={(value) => {
if (!value) return;
setThemeMode(value as "system" | "light" | "dark");
}}
aria-label="Theme mode"
>
<ToggleGroupItem value="system" aria-label="System theme">
<Monitor className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">System</span>
</ToggleGroupItem>
<ToggleGroupItem value="light" aria-label="Light theme">
<Sun className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Light</span>
</ToggleGroupItem>
<ToggleGroupItem value="dark" aria-label="Dark theme">
<Moon className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">Dark</span>
</ToggleGroupItem>
</ToggleGroup>
</div>
{isAuthenticated && me ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -411,22 +439,6 @@ export default function Header() {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="user-dropdown-content">
<div className="user-dropdown-section-label">Theme</div>
{THEME_MODE_ITEMS.map(({ mode: themeMode, label, Icon }) => (
<DropdownMenuItem
key={themeMode}
className="user-dropdown-theme-item"
data-status={mode === themeMode ? "active" : undefined}
onClick={() => setThemeMode(themeMode)}
>
<Icon size={14} aria-hidden="true" />
<span>{label}</span>
{mode === themeMode ? (
<span className="user-dropdown-current">Current</span>
) : null}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link to="/dashboard" className="flex items-center gap-2">
<LayoutDashboard size={14} aria-hidden="true" />
@@ -511,6 +523,55 @@ export default function Header() {
/>
</form>
) : null}
<nav className="navbar-tabs" aria-label="Content types">
<div className="navbar-tabs-primary">
{PRIMARY_NAV_ITEMS.map((item) => {
const Icon = item.icon ? NAV_ICONS[item.icon] : null;
const isActiveByPrefix = item.activePathPrefixes?.some((prefix) =>
location.pathname.startsWith(prefix),
);
return (
<Link
key={item.to + item.label}
to={item.to}
className="navbar-tab"
search={(item.search ?? {}) as never}
data-status={isActiveByPrefix ? "active" : undefined}
>
{Icon ? <Icon size={14} className="opacity-50" aria-hidden="true" /> : null}
{item.label}
</Link>
);
})}
</div>
<div className="navbar-tabs-secondary">
{SECONDARY_NAV_ITEMS.map((item) => {
const isActiveByPrefix = item.activePathPrefixes?.some((prefix) =>
location.pathname.startsWith(prefix),
);
return item.href ? (
<a
key={item.href + item.label}
href={item.href}
className="navbar-tab navbar-tab-secondary"
>
{item.label}
</a>
) : (
<Link
key={(item.to ?? "") + item.label}
to={item.to}
search={(item.search ?? {}) as never}
className="navbar-tab navbar-tab-secondary"
data-status={isActiveByPrefix ? "active" : undefined}
>
{item.label}
</Link>
);
})}
</div>
</nav>
</div>
</header>
);
@@ -692,3 +753,15 @@ function getCurrentRelativeUrl() {
if (typeof window === "undefined") return "/";
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
}
function getThemeModeIcon(mode: "system" | "light" | "dark") {
switch (mode) {
case "light":
return Sun;
case "dark":
return Moon;
case "system":
default:
return Monitor;
}
}
+1 -43
View File
@@ -5,15 +5,6 @@ import { buildDocsAuthCallbackUrl, normalizeDocsReturnTo } from "./docsAuth";
describe("docs auth helpers", () => {
it("allows documentation return URLs and rejects unrelated origins", () => {
expect(normalizeDocsReturnTo("https://clawhub.ai/docs/auth")).toBe(
"https://docs.clawhub.ai/auth",
);
expect(normalizeDocsReturnTo("https://hub.openclaw.ai/docs/auth?from=mobile")).toBe(
"https://docs.clawhub.ai/auth?from=mobile",
);
expect(normalizeDocsReturnTo("https://docs.clawhub.ai/auth?from=mobile")).toBe(
"https://docs.clawhub.ai/auth?from=mobile",
);
expect(normalizeDocsReturnTo("https://documentation.openclaw.ai/concepts/models")).toBe(
"https://documentation.openclaw.ai/concepts/models",
);
@@ -24,29 +15,7 @@ describe("docs auth helpers", () => {
expect(normalizeDocsReturnTo("javascript:alert(1)")).toBeNull();
});
it("rejects local return URLs from production app origins", () => {
expect(
normalizeDocsReturnTo("http://127.0.0.1:16754/docs", {
currentOrigin: "https://clawhub.ai",
}),
).toBeNull();
expect(
buildDocsAuthCallbackUrl("http://localhost:3000/docs/start", {
currentOrigin: "https://clawhub.ai",
}),
).toBeNull();
});
it("keeps callbacks on the same docs host", () => {
expect(buildDocsAuthCallbackUrl("https://clawhub.ai/docs/auth")).toBe(
"https://docs.clawhub.ai/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("https://hub.openclaw.ai/docs/auth")).toBe(
"https://docs.clawhub.ai/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("https://docs.clawhub.ai/auth")).toBe(
"https://docs.clawhub.ai/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("https://documentation.openclaw.ai/concepts/models")).toBe(
"https://documentation.openclaw.ai/ask-molty/auth/callback",
);
@@ -56,19 +25,8 @@ describe("docs auth helpers", () => {
});
it("keeps local callbacks local for dev", () => {
const localApp = { currentOrigin: "http://127.0.0.1:16754" };
expect(buildDocsAuthCallbackUrl("http://localhost:3000/docs/start", localApp)).toBe(
"http://localhost:3000/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("http://127.0.0.1:3000/docs/start", localApp)).toBe(
"http://127.0.0.1:3000/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("http://localhost:4173/start", localApp)).toBe(
expect(buildDocsAuthCallbackUrl("http://localhost:4173/start")).toBe(
"http://localhost:4173/ask-molty/auth/callback",
);
expect(buildDocsAuthCallbackUrl("http://127.0.0.1:16754/docs", localApp)).toBe(
"http://127.0.0.1:16754/ask-molty/auth/callback",
);
});
});
+7 -39
View File
@@ -1,57 +1,25 @@
const canonicalDocsOrigin = "https://docs.clawhub.ai";
const legacyClawHubOrigins = new Set(["https://clawhub.ai", "https://hub.openclaw.ai"]);
const productionDocsOrigins = new Set([
canonicalDocsOrigin,
const allowedDocsOrigins = new Set([
"https://documentation.openclaw.ai",
"https://docs.openclaw.ai",
"http://localhost:4173",
"http://127.0.0.1:4173",
]);
type DocsAuthOptions = {
currentOrigin?: string | null;
};
export function normalizeDocsReturnTo(value?: string | null, options: DocsAuthOptions = {}) {
export function normalizeDocsReturnTo(value?: string | null) {
if (!value) return null;
try {
const url = new URL(value);
if (!allowedDocsOrigins.has(url.origin)) return null;
if (!["http:", "https:"].includes(url.protocol)) return null;
if (legacyClawHubOrigins.has(url.origin)) {
if (url.pathname !== "/docs" && !url.pathname.startsWith("/docs/")) return null;
const canonical = new URL(canonicalDocsOrigin);
url.protocol = canonical.protocol;
url.host = canonical.host;
url.pathname = url.pathname.slice("/docs".length) || "/";
} else if (!isAllowedDocsOrigin(url.origin, options.currentOrigin ?? getCurrentOrigin())) {
return null;
}
return url.href;
} catch {
return null;
}
}
export function buildDocsAuthCallbackUrl(returnTo: string, options: DocsAuthOptions = {}) {
const normalized = normalizeDocsReturnTo(returnTo, options);
export function buildDocsAuthCallbackUrl(returnTo: string) {
const normalized = normalizeDocsReturnTo(returnTo);
if (!normalized) return null;
const url = new URL(normalized);
return `${url.origin}/ask-molty/auth/callback`;
}
function isAllowedDocsOrigin(origin: string, currentOrigin?: string | null) {
if (productionDocsOrigins.has(origin)) return true;
return isLocalDocsOrigin(origin) && isLocalDocsOrigin(currentOrigin ?? "");
}
function isLocalDocsOrigin(origin: string) {
try {
const url = new URL(origin);
return url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname);
} catch {
return false;
}
}
function getCurrentOrigin() {
if (typeof window === "undefined") return null;
return window.location.origin;
}
+6
View File
@@ -11,5 +11,11 @@ export const MARKETPLACE_KIND_ICONS = {
org: Building2,
} as const satisfies Record<MarketplaceIconKind, MarketplaceIconComponent>;
export const NAV_ICONS = {
wrench: Wrench,
plug: MARKETPLACE_KIND_ICONS.plugin,
user: MARKETPLACE_KIND_ICONS.user,
} as const satisfies Record<string, MarketplaceIconComponent>;
export const SKILL_NAV_ICON = Wrench;
export const PLUGIN_NAV_ICON = MARKETPLACE_KIND_ICONS.plugin;
+18 -3
View File
@@ -3,6 +3,9 @@
* triple duplication of nav link definitions.
*/
/** Lucide icon name used as a key to look up the component at render time. */
type NavIconName = "wrench" | "plug" | "user";
interface NavItemBase {
/** Visible link text */
label: string;
@@ -16,13 +19,16 @@ interface RouteNavItem extends NavItemBase {
href?: never;
/** Optional search params object passed to `<Link search>` */
search?: Record<string, unknown>;
/** Optional lucide icon name shown beside the label in navbar tabs */
icon?: NavIconName;
}
interface ExternalNavItem extends NavItemBase {
/** URL rendered as a normal anchor, including external and static app paths. */
/** External URL rendered as a normal anchor */
href: string;
to?: never;
search?: never;
icon?: never;
}
type NavItem = RouteNavItem | ExternalNavItem;
@@ -40,6 +46,8 @@ const SKILLS_SEARCH = {
focus: undefined,
} as const;
const PUBLISHERS_SEARCH = { q: undefined } as const;
// ---------------------------------------------------------------------------
// Primary nav items (desktop tabs row + mobile dropdown top section)
// These map to the content-type tabs: Skills | Plugins | Publishers
@@ -50,13 +58,21 @@ export const PRIMARY_NAV_ITEMS: NavItem[] = [
label: "Skills",
to: "/skills",
search: SKILLS_SEARCH,
icon: "wrench",
activePathPrefixes: ["/skill/"],
},
{
label: "Plugins",
to: "/plugins",
icon: "plug",
activePathPrefixes: ["/plugin/"],
},
{
label: "Publishers",
to: "/publishers",
search: PUBLISHERS_SEARCH,
icon: "user",
},
];
// ---------------------------------------------------------------------------
@@ -66,8 +82,7 @@ export const PRIMARY_NAV_ITEMS: NavItem[] = [
export const SECONDARY_NAV_ITEMS: NavItem[] = [
{
label: "Docs",
href: "https://docs.clawhub.ai/",
activePathPrefixes: ["/docs"],
href: "https://docs.openclaw.ai/clawhub/",
},
];
+6 -28
View File
@@ -9,7 +9,6 @@ let mockAuthStatus = {
isLoading: false,
me: { _id: "user_123" } as { _id: string } | null,
};
let routePath: string | null = null;
vi.mock("@tanstack/react-router", () => ({
createFileRoute: (path: string) => (config: { component: unknown }) => ({
@@ -31,11 +30,6 @@ vi.mock("../../lib/useAuthError", () => ({
useAuthError: () => ({ error: null, clear: vi.fn() }),
}));
vi.mock("../../lib/runtimeEnv", () => ({
getRuntimeEnv: (name: string) =>
name === "VITE_CONVEX_SITE_URL" ? "http://127.0.0.1:3211" : undefined,
}));
vi.mock("../../components/layout/Container", () => ({
Container: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
@@ -69,8 +63,7 @@ const { DocsAuth, Route } = await import("./docs");
describe("DocsAuth", () => {
beforeEach(() => {
routePath = Route.path;
mockSearch = { return_to: "https://docs.clawhub.ai/concepts/models" };
mockSearch = { return_to: "https://docs.openclaw.ai/concepts/models" };
mockAuthToken = "convex.jwt";
mockAuthStatus = {
isAuthenticated: true,
@@ -79,8 +72,8 @@ describe("DocsAuth", () => {
};
});
it("mounts outside /docs so public docs/auth can own the docs page", () => {
expect(routePath).toBe("/auth/docs");
it("mounts outside /docs so OpenClaw docs pages can own the docs route tree", () => {
expect(Route.path).toBe("/auth/docs");
});
it("posts the ClawHub auth token to the docs callback", () => {
@@ -88,27 +81,12 @@ describe("DocsAuth", () => {
const form = screen.getByRole("button", { name: /continue to docs/i }).closest("form");
expect(form?.getAttribute("method")).toBe("post");
expect(form?.getAttribute("action")).toBe("https://docs.clawhub.ai/ask-molty/auth/callback");
expect(form?.getAttribute("action")).toBe("https://docs.openclaw.ai/ask-molty/auth/callback");
expect(document.querySelector<HTMLInputElement>('input[name="token"]')?.value).toBe(
"convex.jwt",
);
expect(document.querySelector<HTMLInputElement>('input[name="return_to"]')?.value).toBe(
"https://docs.clawhub.ai/concepts/models",
);
expect(document.querySelector<HTMLInputElement>('input[name="registry"]')?.value).toBe(
"http://127.0.0.1:3211",
);
});
it("canonicalizes the legacy docs host before posting the auth token", () => {
mockSearch = { return_to: "https://hub.openclaw.ai/docs/plugins?from=mobile" };
render(<DocsAuth autoSubmit={false} />);
const form = screen.getByRole("button", { name: /continue to docs/i }).closest("form");
expect(form?.getAttribute("action")).toBe("https://docs.clawhub.ai/ask-molty/auth/callback");
expect(document.querySelector<HTMLInputElement>('input[name="return_to"]')?.value).toBe(
"https://docs.clawhub.ai/plugins?from=mobile",
"https://docs.openclaw.ai/concepts/models",
);
});
@@ -119,7 +97,7 @@ describe("DocsAuth", () => {
expect(screen.getByRole("heading", { name: /verify with github/i })).toBeTruthy();
expect(screen.getByRole("button", { name: /verify with github/i }).dataset.redirectTo).toBe(
"/auth/docs?return_to=https%3A%2F%2Fdocs.clawhub.ai%2Fconcepts%2Fmodels",
"/auth/docs?return_to=https%3A%2F%2Fdocs.openclaw.ai%2Fconcepts%2Fmodels",
);
});
+5 -14
View File
@@ -8,7 +8,6 @@ import { AuthFlowSkeleton } from "../../components/skeletons/ProtectedPageSkelet
import { Button } from "../../components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
import { buildDocsAuthCallbackUrl, normalizeDocsReturnTo } from "../../lib/docsAuth";
import { getRuntimeEnv } from "../../lib/runtimeEnv";
import { getClawHubSiteUrl, normalizeClawHubSiteOrigin } from "../../lib/site";
import { useAuthError } from "../../lib/useAuthError";
import { useAuthStatus } from "../../lib/useAuthStatus";
@@ -28,20 +27,12 @@ export function DocsAuth({ autoSubmit = true }: DocsAuthProps = {}) {
const formRef = useRef<HTMLFormElement>(null);
const submittedRef = useRef(false);
const search = Route.useSearch() as { return_to?: string };
const currentOrigin =
typeof window !== "undefined"
? window.location.origin
: normalizeClawHubSiteOrigin(getRuntimeEnv("VITE_CONVEX_SITE_URL"));
const docsAuthOptions = { currentOrigin };
const returnTo = normalizeDocsReturnTo(search.return_to, docsAuthOptions);
const callbackUrl = returnTo ? buildDocsAuthCallbackUrl(returnTo, docsAuthOptions) : null;
const returnTo = normalizeDocsReturnTo(search.return_to);
const callbackUrl = returnTo ? buildDocsAuthCallbackUrl(returnTo) : null;
const signInRedirectTo = returnTo
? `/auth/docs?return_to=${encodeURIComponent(returnTo)}`
: undefined;
const registry = useMemo(() => {
const localConvexSiteUrl = normalizeClawHubSiteOrigin(getRuntimeEnv("VITE_CONVEX_SITE_URL"));
if (localConvexSiteUrl?.startsWith("http://localhost:")) return localConvexSiteUrl;
if (localConvexSiteUrl?.startsWith("http://127.0.0.1:")) return localConvexSiteUrl;
if (typeof window !== "undefined") {
return normalizeClawHubSiteOrigin(window.location.origin) ?? getClawHubSiteUrl();
}
@@ -62,7 +53,7 @@ export function DocsAuth({ autoSubmit = true }: DocsAuthProps = {}) {
<AuthFrame title="Docs agent login">
<p className="text-sm text-[color:var(--ink-soft)]">Invalid docs return URL.</p>
<p className="text-sm text-[color:var(--ink-soft)]">
Open Ask Molty from the ClawHub documentation page and try again.
Open Ask Molty from the OpenClaw documentation page and try again.
</p>
</AuthFrame>
);
@@ -76,7 +67,7 @@ export function DocsAuth({ autoSubmit = true }: DocsAuthProps = {}) {
return (
<SignInPrompt
title="Verify with GitHub"
description="Sign in to ClawHub with GitHub to unlock Ask Molty on the ClawHub docs."
description="Sign in to ClawHub with GitHub to unlock Ask Molty on the OpenClaw docs."
error={authError}
onDismissError={clearAuthError}
action={
@@ -99,7 +90,7 @@ export function DocsAuth({ autoSubmit = true }: DocsAuthProps = {}) {
return (
<AuthFrame title="Connecting docs">
<p className="text-sm text-[color:var(--ink-soft)]">
Returning to the ClawHub docs with your ClawHub login.
Returning to the OpenClaw docs with your ClawHub login.
</p>
<form ref={formRef} method="post" action={callbackUrl ?? undefined}>
<input type="hidden" name="token" value={authToken} />
+1 -1
View File
@@ -60,7 +60,7 @@ const apiRefs = api as unknown as {
};
const SHOW_CLAWPACK_ONBOARDING_BANNER = false;
const PLUGIN_PUBLISHING_GUIDE_URL = "https://docs.clawhub.ai/publishing#plugins";
const PLUGIN_PUBLISHING_GUIDE_URL = "https://docs.openclaw.ai/clawhub/publishing#plugins";
function findReadmeFile(files: File[]): File | null {
// Match the same lookup the publish backend uses (readme.md / readme.mdx)
+1 -1
View File
@@ -52,7 +52,7 @@ import {
} from "../upload/-utils";
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const SKILL_PUBLISHING_GUIDE_URL = "https://docs.clawhub.ai/skill-format";
const SKILL_PUBLISHING_GUIDE_URL = "https://docs.openclaw.ai/clawhub/skill-format";
const REQUIRED_FILE_LABEL = "SKILL.md";
const REQUIRED_FILE_ISSUE = `${REQUIRED_FILE_LABEL} is required.`;
// Ordered by sort priority: the canonical name first, the legacy alias second.
+118 -58
View File
@@ -427,10 +427,9 @@ code {
.navbar-top {
display: grid;
grid-template-columns: max-content max-content minmax(220px, 1fr) auto;
grid-template-columns: minmax(148px, auto) minmax(240px, 1fr) auto;
align-items: center;
column-gap: 16px;
row-gap: 10px;
gap: 16px;
padding: 12px 0;
min-height: 62px;
}
@@ -654,7 +653,7 @@ code {
}
.navbar-top-links {
display: inline-flex;
display: none;
align-items: center;
justify-content: flex-start;
gap: 4px;
@@ -662,11 +661,6 @@ code {
white-space: nowrap;
}
.navbar-top-links .navbar-tab {
min-height: 32px;
padding: 7px 10px;
}
.navbar-tabs-primary,
.navbar-tabs-secondary {
display: inline-flex;
@@ -920,6 +914,103 @@ code {
min-width: 0;
}
.theme-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
min-width: 0;
justify-content: flex-end;
}
.theme-cycle-group {
display: none;
align-items: center;
gap: 8px;
width: 72px;
min-width: 72px;
grid-template-columns: repeat(2, 32px);
justify-content: end;
}
.theme-cycle-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid var(--line);
border-radius: var(--r-btn);
background: var(--surface);
color: var(--ink-soft);
cursor: pointer;
transition:
border-color 0.15s ease,
background 0.15s ease,
color 0.15s ease,
box-shadow 0.15s ease;
}
.theme-cycle-button:hover {
color: var(--ink);
border-color: var(--border-ui-hover);
background: var(--surface-muted);
}
.theme-cycle-button:focus-visible {
outline: 2px solid var(--input-focus-border);
outline-offset: 2px;
}
.theme-mode-toggle {
min-width: 124px;
height: 32px;
min-height: 32px;
padding: 0 8px;
gap: 8px;
border: 1px solid var(--line);
background: var(--surface);
justify-content: center;
border-radius: var(--r-btn);
}
.theme-mode-toggle button {
all: unset;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: var(--r-btn);
cursor: pointer;
color: var(--ink-soft);
opacity: 0.5;
transition:
opacity 0.15s ease,
color 0.15s ease,
background 0.15s ease;
}
.theme-mode-toggle button svg {
width: 16px;
height: 16px;
}
.theme-mode-toggle button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.theme-mode-toggle button:hover {
opacity: 0.8;
}
.theme-mode-toggle button[data-state="on"] {
opacity: 1;
color: var(--ink);
background: transparent;
}
.github-sign-in-button {
height: 32px !important;
min-height: 32px !important;
@@ -1016,30 +1107,6 @@ code {
outline: none;
}
.user-dropdown-section-label {
padding: 4px 8px 6px;
font-size: var(--fs-xs);
font-weight: 650;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-soft);
}
.user-dropdown-theme-item {
min-width: 220px;
}
.user-dropdown-theme-item[data-status="active"] {
background: color-mix(in srgb, var(--accent) 12%, transparent);
color: var(--ink);
}
.user-dropdown-current {
margin-left: auto;
font-size: var(--fs-xs);
color: var(--ink-soft);
}
/* @deprecated — use <Button> from components/ui/button.tsx */
.btn {
border: 1px solid var(--border-ui);
@@ -6552,6 +6619,10 @@ code {
display: none;
}
.theme-toggle {
display: none;
}
.nav-actions {
justify-self: end;
margin-left: 0;
@@ -13414,35 +13485,17 @@ a.publisher-profile-detail:hover {
}
@media (max-width: 1100px) and (min-width: 761px) {
.navbar-top {
grid-template-columns: max-content max-content minmax(180px, 1fr) auto;
gap: 10px;
.theme-mode-toggle {
min-width: 112px;
height: 40px;
min-height: 40px;
padding: 0 8px;
gap: 6px;
}
.brand-name-responsive {
.theme-cycle-group {
display: none;
}
.navbar-top-links {
gap: 0;
}
.navbar-top-links .navbar-tab {
padding: 7px 8px;
}
.github-sign-in-button,
.auth-loading-placeholder {
width: 92px !important;
}
.sign-in-full-copy {
display: none;
}
.sign-in-compact-copy {
display: inline-block;
}
}
/*
@@ -14970,6 +15023,13 @@ a.publisher-profile-detail:hover {
border-radius: var(--r-btn) !important;
}
/* Theme toggle buttons */
.theme-cycle-button,
.theme-mode-toggle button,
.theme-mode-toggle {
border-radius: var(--r-btn) !important;
}
/* Management action buttons */
.meta-bar-footer > .btn {
border-radius: var(--r-btn) !important;
+1 -130
View File
@@ -5,7 +5,7 @@
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self' https://clawhub.ai https://docs.clawhub.ai https://documentation.openclaw.ai https://docs.openclaw.ai http://localhost:3000 http://127.0.0.1:3000 http://localhost:4173 http://127.0.0.1:4173; upgrade-insecure-requests"
"value": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self' https://documentation.openclaw.ai https://docs.openclaw.ai http://localhost:4173 http://127.0.0.1:4173; upgrade-insecure-requests"
},
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
@@ -21,102 +21,6 @@
}
],
"redirects": [
{
"source": "/docs",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/",
"statusCode": 308
},
{
"source": "/docs/",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/",
"statusCode": 308
},
{
"source": "/docs/:path*",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/:path*",
"statusCode": 308
},
{
"source": "/docs/:path*/",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/:path*/",
"statusCode": 308
},
{
"source": "/docs",
"has": [
{
"type": "header",
"key": "host",
"value": "clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/",
"statusCode": 308
},
{
"source": "/docs/",
"has": [
{
"type": "header",
"key": "host",
"value": "clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/",
"statusCode": 308
},
{
"source": "/docs/:path*",
"has": [
{
"type": "header",
"key": "host",
"value": "clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/:path*",
"statusCode": 308
},
{
"source": "/docs/:path*/",
"has": [
{
"type": "header",
"key": "host",
"value": "clawhub.ai"
}
],
"destination": "https://docs.clawhub.ai/:path*/",
"statusCode": 308
},
{
"source": "/",
"has": [
@@ -143,39 +47,6 @@
}
],
"rewrites": [
{
"source": "/",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "/docs"
},
{
"source": "/ask-molty/:path*",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "https://clawhub.ai/ask-molty/:path*"
},
{
"source": "/:path*",
"has": [
{
"type": "header",
"key": "host",
"value": "docs.clawhub.ai"
}
],
"destination": "/docs/:path*"
},
{
"source": "/api/:path*",
"destination": "https://wry-manatee-359.convex.site/api/:path*"
-12
View File
@@ -15,7 +15,6 @@ const convexReactPath = join(convexRoot, "dist/esm/react/index.js");
const convexBrowserPath = join(convexRoot, "dist/esm/browser/index.js");
const convexValuesPath = join(convexRoot, "dist/esm/values/index.js");
const convexAuthReactPath = require.resolve("@convex-dev/auth/react");
const askMoltyProxyOrigin = process.env.ASK_MOLTY_PROXY_ORIGIN;
function handleRollupWarning(
warning: { code?: string; message: string; id?: string },
@@ -196,17 +195,6 @@ const config = defineConfig({
tanstackStart(),
viteReact(),
],
server: {
proxy: askMoltyProxyOrigin
? {
"/ask-molty": {
target: askMoltyProxyOrigin,
changeOrigin: true,
secure: false,
},
}
: undefined,
},
build: {
// Keep the shipped client bundle parseable in Safari/WebKit.
target: "safari15",