mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
docs: restore OpenClaw-hosted ClawHub docs (#2631)
* Revert "fix: serve docs subdomain root (#2630)" This reverts commit9ef33726b8. * Revert "feat: move docs to canonical subdomain" This reverts commit6afb91b772. * Revert "docs: serve ClawHub docs with shared OpenClaw renderer (#2609)" This reverts commit71156751ff. * 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:
@@ -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"}'
|
||||
@@ -9,7 +9,6 @@ artifacts/
|
||||
bin/docs-list
|
||||
dist
|
||||
dist-ssr
|
||||
public/docs/
|
||||
!packages/schema/dist
|
||||
!packages/schema/dist/**
|
||||
*.local
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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'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,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;
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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(() => ({
|
||||
|
||||
@@ -18,7 +18,7 @@ const resolveHandler = (
|
||||
>
|
||||
)._handler;
|
||||
|
||||
function chain(value: unknown) {
|
||||
function chain<T>(value: T) {
|
||||
const q = {
|
||||
eq: vi.fn(() => q),
|
||||
};
|
||||
|
||||
+6
-9
@@ -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 |
@@ -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": []
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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.",
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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") });
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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("; ");
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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/",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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*"
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user