mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
feat: move docs to canonical subdomain
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# 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
|
||||
|
||||
+10
-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:\/\/(?:clawhub\.ai\/docs|docs\.openclaw\.ai)"[^>]*style="[^"]*color:#8a8a8e[^"]*text-decoration[^"]*underline[^"]*"[^>]*>Docs<\/a>/,
|
||||
/href="https:\/\/(?:docs\.clawhub\.ai|docs\.openclaw\.ai)"[^>]*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://clawhub.ai/docs");
|
||||
expect(email.html).toContain("https://docs.clawhub.ai");
|
||||
expectFooterLinksUnderlined(email.html);
|
||||
expect(email.text).not.toContain("clawhub scan ./my-skill --output clawhub-scan.zip");
|
||||
expect(email.text).not.toContain("https://clawhub.ai/docs/cli#scan-path");
|
||||
expect(email.text).not.toContain("https://docs.clawhub.ai/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://clawhub.ai/docs");
|
||||
expect(email.html).toContain("https://docs.clawhub.ai");
|
||||
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://clawhub.ai/docs/moderation");
|
||||
expect(email.text).toContain("https://docs.clawhub.ai/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,7 @@ describe("moderation notification email copy", () => {
|
||||
scanSource: "publish",
|
||||
authorRemediation: {
|
||||
summary: "Replace the legacy before_agent_start hook with current prompt hooks.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -242,7 +242,7 @@ describe("moderation notification email copy", () => {
|
||||
);
|
||||
expect(email.text).toContain(" Docs:");
|
||||
expect(email.text).toContain(
|
||||
" https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
" https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
);
|
||||
expect(email.text).not.toContain("ClawHub Security");
|
||||
expect(email.html).toContain("Validate a local fix");
|
||||
@@ -251,7 +251,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://clawhub.ai/docs/plugin-validation-fixes");
|
||||
expect(email.html).toContain("https://docs.clawhub.ai/plugin-validation-fixes");
|
||||
expectFooterLinksUnderlined(email.html);
|
||||
expect(email.html).toContain("OpenClaw Version");
|
||||
expect(email.html).toContain("0.9.0");
|
||||
@@ -350,7 +350,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://clawhub.ai/docs");
|
||||
expect(email.html).toContain("https://docs.clawhub.ai");
|
||||
expectFooterLinksUnderlined(email.html);
|
||||
});
|
||||
|
||||
@@ -369,7 +369,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://clawhub.ai/docs");
|
||||
expect(email.html).toContain("https://docs.clawhub.ai");
|
||||
expectFooterLinksUnderlined(email.html);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const APPEALS_URL = "https://appeals.openclaw.ai/";
|
||||
export const MODERATION_GUIDELINES_URL = "https://clawhub.ai/docs/moderation";
|
||||
export const MODERATION_GUIDELINES_URL = "https://docs.clawhub.ai/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,7 @@ 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://clawhub.ai/docs/plugin-validation-fixes#package-entrypoint-missing",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#package-entrypoint-missing",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -75,7 +75,7 @@ describe("package inspector publish normalization", () => {
|
||||
authorRemediation: {
|
||||
summary: "Declare the OpenClaw plugin API range this package supports.",
|
||||
docsUrl:
|
||||
"https://clawhub.ai/docs/plugin-validation-fixes#package-plugin-api-compat-missing",
|
||||
"https://docs.clawhub.ai/plugin-validation-fixes#package-plugin-api-compat-missing",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -41,7 +41,7 @@ type InspectorReport = {
|
||||
issues?: unknown[];
|
||||
};
|
||||
|
||||
const AUTHOR_REMEDIATION_DOCS_BASE = "https://clawhub.ai/docs/plugin-validation-fixes";
|
||||
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.clawhub.ai/plugin-validation-fixes";
|
||||
|
||||
const LEGACY_AUTHOR_REMEDIATION_SUMMARIES = {
|
||||
"channel-env-vars":
|
||||
|
||||
@@ -6757,7 +6757,7 @@ describe("packages public queries", () => {
|
||||
evidence: ["src/index.ts:4"],
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -6851,7 +6851,7 @@ describe("packages public queries", () => {
|
||||
message: "legacy before_agent_start hook is deprecated",
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -6911,7 +6911,7 @@ describe("packages public queries", () => {
|
||||
fixture: "demo",
|
||||
authorRemediation: {
|
||||
summary: "Add a display name to the plugin manifest.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#manifest-name-missing",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#manifest-name-missing",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -6926,7 +6926,7 @@ describe("packages public queries", () => {
|
||||
inspectorFindingId: "demo:manifest-name-missing",
|
||||
authorRemediation: {
|
||||
summary: "Add a display name to the plugin manifest.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#manifest-name-missing",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#manifest-name-missing",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -6982,7 +6982,7 @@ describe("packages public queries", () => {
|
||||
fixture: "demo",
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -7040,7 +7040,7 @@ describe("packages public queries", () => {
|
||||
fixture: "demo",
|
||||
authorRemediation: {
|
||||
summary: "Replace registerTool with the current plugin API.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#missing-expected-seam",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#missing-expected-seam",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -7108,7 +7108,7 @@ describe("packages public queries", () => {
|
||||
fixture: "demo",
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -7123,7 +7123,7 @@ describe("packages public queries", () => {
|
||||
issueClass: "deprecation-warning",
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -7148,7 +7148,7 @@ describe("packages public queries", () => {
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl:
|
||||
"https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
"https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
]),
|
||||
@@ -7240,7 +7240,7 @@ describe("packages public queries", () => {
|
||||
authorRemediation: {
|
||||
summary: "Replace registerTool with the current plugin API.",
|
||||
docsUrl:
|
||||
"https://clawhub.ai/docs/plugin-validation-fixes#missing-expected-seam",
|
||||
"https://docs.clawhub.ai/plugin-validation-fixes#missing-expected-seam",
|
||||
},
|
||||
createdAt: 2,
|
||||
},
|
||||
@@ -7410,7 +7410,7 @@ describe("packages public queries", () => {
|
||||
authorRemediation: {
|
||||
summary: "Move prompt mutation work to before_prompt_build.",
|
||||
docsUrl:
|
||||
"https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
"https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
createdAt: 1,
|
||||
},
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ read_when:
|
||||
# Docs
|
||||
|
||||
`docs/` is the publishable source for user-facing ClawHub documentation served
|
||||
under `https://clawhub.ai/docs`.
|
||||
at `https://docs.clawhub.ai`.
|
||||
|
||||
Keep product, CLI, publisher, API, policy, security, and troubleshooting docs
|
||||
here. These pages should explain how people use ClawHub: discover, install,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const CLAWHUB_URL = "https://clawhub.ai";
|
||||
export const CLAWHUB_DOCS_URL = "https://clawhub.ai/docs";
|
||||
export const CLAWHUB_DOCS_URL = "https://docs.clawhub.ai";
|
||||
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://clawhub.ai/docs/moderation",
|
||||
docsUrl: "https://docs.clawhub.ai/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://clawhub.ai/docs");
|
||||
expect(html).toContain("https://docs.clawhub.ai");
|
||||
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://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
],
|
||||
} satisfies PluginInspectorFindingsEmailProps;
|
||||
|
||||
@@ -386,7 +386,7 @@ describe("package commands", () => {
|
||||
evidence: ["src/index.ts:4", { hook: "before_agent_start" }],
|
||||
authorRemediation: {
|
||||
summary: "Move the hook to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-hook",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -414,7 +414,7 @@ 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://clawhub.ai/docs/plugin-validation-fixes#legacy-hook");
|
||||
expect(output).toContain("Docs: https://docs.clawhub.ai/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 +451,7 @@ describe("package commands", () => {
|
||||
message: "legacy hook is deprecated",
|
||||
authorRemediation: {
|
||||
summary: "Move the hook to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-hook",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -489,7 +489,7 @@ describe("package commands", () => {
|
||||
message: "legacy hook is deprecated",
|
||||
authorRemediation: {
|
||||
summary: "Move the hook to before_prompt_build.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-hook",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-hook",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -499,7 +499,7 @@ describe("package commands", () => {
|
||||
authorRemediation: {
|
||||
summary: "Declare the OpenClaw plugin API range this package supports.",
|
||||
docsUrl:
|
||||
"https://clawhub.ai/docs/plugin-validation-fixes#package-plugin-api-compat-missing",
|
||||
"https://docs.clawhub.ai/plugin-validation-fixes#package-plugin-api-compat-missing",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -2488,7 +2488,7 @@ 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://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -2510,7 +2510,7 @@ describe("package commands", () => {
|
||||
" Fix: Replace the legacy before_agent_start hook with current prompt hooks.",
|
||||
);
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
" Docs: https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
" Docs: https://docs.clawhub.ai/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://clawhub.ai/docs/plugin-validation-fixes";
|
||||
const AUTHOR_REMEDIATION_DOCS_BASE = "https://docs.clawhub.ai/plugin-validation-fixes";
|
||||
const LEGACY_AUTHOR_REMEDIATION_SUMMARIES = {
|
||||
"channel-env-vars":
|
||||
"Move legacy channel environment variable metadata into the current setup/config metadata.",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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.
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -24,6 +24,18 @@ export function planDocsStage(sourceRels) {
|
||||
.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,
|
||||
@@ -90,7 +102,7 @@ function prepareStage({ docsRepoDir, stageRoot, cwd }) {
|
||||
if (entry.injectSourcePath) {
|
||||
fs.writeFileSync(
|
||||
target,
|
||||
injectSourcePath(fs.readFileSync(source, "utf8"), entry.injectSourcePath),
|
||||
prepareDocsMarkdown(fs.readFileSync(source, "utf8"), entry.injectSourcePath),
|
||||
"utf8",
|
||||
);
|
||||
} else {
|
||||
@@ -109,10 +121,10 @@ function runBuilderPipeline({ docsRepoDir, stageRoot, cwd, commandRunner, env })
|
||||
const sourceRepoDir = env.DOCS_SOURCE_REPO_DIR?.trim() || cwd;
|
||||
const builderEnv = {
|
||||
...env,
|
||||
DOCS_SITE_BASE_PATH: "/docs",
|
||||
DOCS_SITE_BASE_PATH: docsBasePathForEnv(env),
|
||||
DOCS_SITE_LEGACY_BASE_PATH: "",
|
||||
DOCS_SITE_CANONICAL_ORIGIN: "https://clawhub.ai/docs",
|
||||
DOCS_SITE_CHAT_AUTH_URL: "/auth/docs",
|
||||
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",
|
||||
@@ -167,6 +179,11 @@ function copyGeneratedDocs({ stageRoot, outputDir }) {
|
||||
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) {
|
||||
|
||||
@@ -4,7 +4,13 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ensureOpenClawDocsRepo, planDocsStage, resolveOpenClawDocsRepo } from "./docs-builder.mjs";
|
||||
import {
|
||||
docsBasePathForEnv,
|
||||
ensureOpenClawDocsRepo,
|
||||
planDocsStage,
|
||||
prepareDocsMarkdown,
|
||||
resolveOpenClawDocsRepo,
|
||||
} from "./docs-builder.mjs";
|
||||
|
||||
describe("docs-builder", () => {
|
||||
it("stages ClawHub docs in the shape expected by openclaw/docs", () => {
|
||||
@@ -37,6 +43,23 @@ describe("docs-builder", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
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 });
|
||||
|
||||
+6
-12
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
const HERE = resolve(fileURLToPath(import.meta.url), "..");
|
||||
const CLAWHUB_ROOT = resolve(HERE, "..");
|
||||
const PUBLIC_ROOT = resolve(CLAWHUB_ROOT, "public");
|
||||
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");
|
||||
|
||||
@@ -22,13 +22,7 @@ const server = createServer((request, response) => {
|
||||
}
|
||||
|
||||
const url = new URL(request.url, `http://${HOST}:${PORT}`);
|
||||
if (url.pathname === "/") {
|
||||
response.writeHead(302, { Location: "/docs/" });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const file = resolvePublicPath(url.pathname);
|
||||
const file = resolveDocsPath(url.pathname);
|
||||
if (!file) {
|
||||
response.writeHead(404);
|
||||
response.end("Not found");
|
||||
@@ -40,7 +34,7 @@ const server = createServer((request, response) => {
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`ClawHub docs preview: http://${HOST}:${PORT}/docs/`);
|
||||
console.log(`ClawHub docs preview: http://${HOST}:${PORT}/`);
|
||||
});
|
||||
|
||||
function run(command: string, args: string[]) {
|
||||
@@ -54,12 +48,12 @@ function run(command: string, args: string[]) {
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
function resolvePublicPath(pathname: string) {
|
||||
function resolveDocsPath(pathname: string) {
|
||||
const decoded = decodeURIComponent(pathname);
|
||||
const normalized = normalize(decoded).replace(/^(\.\.(?:\/|\\|$))+/, "");
|
||||
let candidate = resolve(PUBLIC_ROOT, `.${sep}${normalized}`);
|
||||
let candidate = resolve(DOCS_ROOT, `.${sep}${normalized}`);
|
||||
|
||||
if (!candidate.startsWith(`${PUBLIC_ROOT}${sep}`) && candidate !== PUBLIC_ROOT) {
|
||||
if (!candidate.startsWith(`${DOCS_ROOT}${sep}`) && candidate !== DOCS_ROOT) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
+39
-13
@@ -13,22 +13,24 @@ 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://clawhub.ai/docs/"'),
|
||||
"index should use clawhub.ai/docs canonical URL",
|
||||
);
|
||||
assert(
|
||||
index.includes('href="/docs/quickstart"'),
|
||||
"index should link to the quickstart child route",
|
||||
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",
|
||||
@@ -43,8 +45,8 @@ 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("/auth/docs",location.href)'),
|
||||
"Ask Molty auth should use ClawHub /auth/docs",
|
||||
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"),
|
||||
@@ -71,25 +73,26 @@ assert(
|
||||
"llms.txt should describe the ClawHub docs corpus",
|
||||
);
|
||||
assert(
|
||||
/https:\/\/clawhub\.ai\/docs\/[^)\s]+\.md/u.test(llms) &&
|
||||
/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://clawhub.ai/docs/sitemap.xml") &&
|
||||
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="/docs/plugin-validation-fixes#package-json-missing"') &&
|
||||
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="/plugins/building-plugins"') &&
|
||||
pluginValidationFixes.includes('href="https://docs.openclaw.ai/plugins/building-plugins"') &&
|
||||
!pluginValidationFixes.includes('href="/docs/plugins/building-plugins"'),
|
||||
"root app links should stay outside the docs base path",
|
||||
"OpenClaw plugin guide links should use the canonical OpenClaw docs host",
|
||||
);
|
||||
assert(
|
||||
cli.includes(
|
||||
@@ -105,6 +108,29 @@ 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 === "/: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");
|
||||
|
||||
|
||||
@@ -60,14 +60,14 @@ describe("ask-molty proxy route", () => {
|
||||
}),
|
||||
);
|
||||
const event = mockEvent(
|
||||
"https://clawhub.ai/ask-molty/sign-in?return_to=https%3A%2F%2Fclawhub.ai%2Fdocs",
|
||||
"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%2Fclawhub.ai%2Fdocs",
|
||||
"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",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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",
|
||||
},
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -10,7 +10,7 @@ Status: superseded for the CLAW-201 ClawHub docs migration.
|
||||
|
||||
The current migration plan is to build `docs/` with the shared
|
||||
`openclaw/docs` renderer and serve the generated output from ClawHub under
|
||||
`https://clawhub.ai/docs`. Do not set up Mintlify for this migration unless a
|
||||
`https://docs.clawhub.ai`. Do not set up Mintlify for this migration unless a
|
||||
new issue explicitly reopens that direction.
|
||||
|
||||
## Historical note
|
||||
|
||||
@@ -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://clawhub.ai/docs` rather than
|
||||
These OpenClaw docs should eventually point to `https://docs.clawhub.ai` rather than
|
||||
restate the whole registry guide:
|
||||
|
||||
- `docs/tools/clawhub.md`: keep as a concise bridge to ClawHub docs plus OpenClaw-native install examples.
|
||||
|
||||
@@ -698,7 +698,7 @@ describe("plugin detail route", () => {
|
||||
scanSource: "nightly",
|
||||
authorRemediation: {
|
||||
summary: "Replace the legacy before_agent_start hook with current prompt hooks.",
|
||||
docsUrl: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
docsUrl: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
},
|
||||
createdAt: 1,
|
||||
},
|
||||
@@ -779,10 +779,10 @@ describe("plugin detail route", () => {
|
||||
expect(
|
||||
screen
|
||||
.getByRole("link", {
|
||||
name: "https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start",
|
||||
name: "https://docs.clawhub.ai/plugin-validation-fixes#legacy-before-agent-start",
|
||||
})
|
||||
.getAttribute("href"),
|
||||
).toBe("https://clawhub.ai/docs/plugin-validation-fixes#legacy-before-agent-start");
|
||||
).toBe("https://docs.clawhub.ai/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,7 @@ describe("plugins publish route", () => {
|
||||
renderPublishRoute();
|
||||
|
||||
const guideLink = screen.getByRole("link", { name: /Plugin publishing guide/i });
|
||||
expect(guideLink.getAttribute("href")).toBe("https://clawhub.ai/docs/publishing#plugins");
|
||||
expect(guideLink.getAttribute("href")).toBe("https://docs.clawhub.ai/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://clawhub.ai/docs/skill-format");
|
||||
expect(guideLink.getAttribute("href")).toBe("https://docs.clawhub.ai/skill-format");
|
||||
expect(guideLink.getAttribute("target")).toBe("_blank");
|
||||
});
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@ 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://clawhub.ai/docs/auth",
|
||||
"https://docs.clawhub.ai/auth",
|
||||
);
|
||||
expect(normalizeDocsReturnTo("https://hub.openclaw.ai/docs/auth?from=mobile")).toBe(
|
||||
"https://clawhub.ai/docs/auth?from=mobile",
|
||||
"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",
|
||||
@@ -36,10 +39,13 @@ describe("docs auth helpers", () => {
|
||||
|
||||
it("keeps callbacks on the same docs host", () => {
|
||||
expect(buildDocsAuthCallbackUrl("https://clawhub.ai/docs/auth")).toBe(
|
||||
"https://clawhub.ai/ask-molty/auth/callback",
|
||||
"https://docs.clawhub.ai/ask-molty/auth/callback",
|
||||
);
|
||||
expect(buildDocsAuthCallbackUrl("https://hub.openclaw.ai/docs/auth")).toBe(
|
||||
"https://clawhub.ai/ask-molty/auth/callback",
|
||||
"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",
|
||||
|
||||
+8
-7
@@ -1,8 +1,7 @@
|
||||
const canonicalDocsOrigin = "https://clawhub.ai";
|
||||
const docsOriginAliases = new Map([["https://hub.openclaw.ai", canonicalDocsOrigin]]);
|
||||
const canonicalDocsOrigin = "https://docs.clawhub.ai";
|
||||
const legacyClawHubOrigins = new Set(["https://clawhub.ai", "https://hub.openclaw.ai"]);
|
||||
const productionDocsOrigins = new Set([
|
||||
canonicalDocsOrigin,
|
||||
...docsOriginAliases.keys(),
|
||||
"https://documentation.openclaw.ai",
|
||||
"https://docs.openclaw.ai",
|
||||
]);
|
||||
@@ -16,12 +15,14 @@ export function normalizeDocsReturnTo(value?: string | null, options: DocsAuthOp
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (!["http:", "https:"].includes(url.protocol)) return null;
|
||||
if (!isAllowedDocsOrigin(url.origin, options.currentOrigin ?? getCurrentOrigin())) return null;
|
||||
const canonicalOrigin = docsOriginAliases.get(url.origin);
|
||||
if (canonicalOrigin) {
|
||||
const canonical = new URL(canonicalOrigin);
|
||||
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 {
|
||||
|
||||
@@ -70,7 +70,7 @@ const { DocsAuth, Route } = await import("./docs");
|
||||
describe("DocsAuth", () => {
|
||||
beforeEach(() => {
|
||||
routePath = Route.path;
|
||||
mockSearch = { return_to: "https://clawhub.ai/docs/concepts/models" };
|
||||
mockSearch = { return_to: "https://docs.clawhub.ai/concepts/models" };
|
||||
mockAuthToken = "convex.jwt";
|
||||
mockAuthStatus = {
|
||||
isAuthenticated: true,
|
||||
@@ -88,12 +88,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://clawhub.ai/ask-molty/auth/callback");
|
||||
expect(form?.getAttribute("action")).toBe("https://docs.clawhub.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://clawhub.ai/docs/concepts/models",
|
||||
"https://docs.clawhub.ai/concepts/models",
|
||||
);
|
||||
expect(document.querySelector<HTMLInputElement>('input[name="registry"]')?.value).toBe(
|
||||
"http://127.0.0.1:3211",
|
||||
@@ -106,9 +106,9 @@ describe("DocsAuth", () => {
|
||||
render(<DocsAuth autoSubmit={false} />);
|
||||
|
||||
const form = screen.getByRole("button", { name: /continue to docs/i }).closest("form");
|
||||
expect(form?.getAttribute("action")).toBe("https://clawhub.ai/ask-molty/auth/callback");
|
||||
expect(form?.getAttribute("action")).toBe("https://docs.clawhub.ai/ask-molty/auth/callback");
|
||||
expect(document.querySelector<HTMLInputElement>('input[name="return_to"]')?.value).toBe(
|
||||
"https://clawhub.ai/docs/plugins?from=mobile",
|
||||
"https://docs.clawhub.ai/plugins?from=mobile",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -119,7 +119,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%2Fclawhub.ai%2Fdocs%2Fconcepts%2Fmodels",
|
||||
"/auth/docs?return_to=https%3A%2F%2Fdocs.clawhub.ai%2Fconcepts%2Fmodels",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ const apiRefs = api as unknown as {
|
||||
};
|
||||
|
||||
const SHOW_CLAWPACK_ONBOARDING_BANNER = false;
|
||||
const PLUGIN_PUBLISHING_GUIDE_URL = "https://clawhub.ai/docs/publishing#plugins";
|
||||
const PLUGIN_PUBLISHING_GUIDE_URL = "https://docs.clawhub.ai/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://clawhub.ai/docs/skill-format";
|
||||
const SKILL_PUBLISHING_GUIDE_URL = "https://docs.clawhub.ai/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.
|
||||
|
||||
+71
-1
@@ -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://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://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"
|
||||
},
|
||||
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
||||
{ "key": "X-Frame-Options", "value": "DENY" },
|
||||
@@ -21,6 +21,54 @@
|
||||
}
|
||||
],
|
||||
"redirects": [
|
||||
{
|
||||
"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": [
|
||||
@@ -47,6 +95,28 @@
|
||||
}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"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*"
|
||||
|
||||
Reference in New Issue
Block a user