From 8c86d6f570b98ce7028aa2b2a9347c906d9fbd57 Mon Sep 17 00:00:00 2001
From: Patrick Erichsen
Date: Thu, 7 May 2026 19:03:40 -0700
Subject: [PATCH] Fix plugin publish ownership visibility (#2073)
* fix: clarify plugin publish ownership state
* test: tolerate publish route migration in prod smoke
* fix: reserve publish route collisions
* fix: preflight package scope owner mismatches in CLI
* fix: keep package scope validation server-side
* docs: explain ClawHub publishing flow
* fix: include publishing docs link in scope errors
* fix: centralize docs links
* fix: build docs links with URL
* fix: shorten package scope docs hint
---
.github/CODEOWNERS | 2 +
.../codeql-frontend-publish-security.yml | 2 +
convex/lib/packageRegistry.test.ts | 6 +
convex/lib/packageRegistry.ts | 7 +
convex/lib/publicRouteReservations.ts | 18 +
convex/packages.public.test.ts | 215 ++++-
convex/packages.ts | 56 +-
convex/publishers.test.ts | 21 +
convex/publishers.ts | 7 +
convex/users.test.ts | 24 +
convex/users.ts | 2 +
docs/README.md | 13 +-
docs/cli.md | 2 +
docs/publishing.md | 85 ++
e2e/ci-smoke.pw.test.ts | 2 +-
e2e/prod-http-smoke.e2e.test.ts | 2 +-
e2e/publish-entry-workflows.pw.test.ts | 2 +-
packages/schema/dist/docsLinks.d.ts | 12 +
packages/schema/dist/docsLinks.js | 16 +
packages/schema/dist/docsLinks.js.map | 1 +
packages/schema/dist/index.d.ts | 1 +
packages/schema/dist/index.js | 1 +
packages/schema/dist/index.js.map | 2 +-
packages/schema/dist/packages.d.ts | 8 +
packages/schema/dist/packages.js | 21 +
packages/schema/dist/packages.js.map | 2 +-
packages/schema/src/docsLinks.ts | 19 +
packages/schema/src/index.ts | 1 +
packages/schema/src/packages.ts | 23 +
packages/schema/src/schemas.test.ts | 26 +
...est.tsx => plugins-publish-route.test.tsx} | 83 +-
src/__tests__/publish-redirect-routes.test.ts | 58 ++
...test.tsx => skills-publish-route.test.tsx} | 2 +-
src/components/Footer.test.tsx | 4 +-
src/components/PackageSourceChooser.tsx | 7 +-
src/components/SkillHeader.tsx | 2 +-
src/lib/nav-items.ts | 4 +-
src/routeTree.gen.ts | 42 +
src/routes/about.tsx | 7 +-
src/routes/dashboard.tsx | 6 +-
src/routes/import.tsx | 2 +-
src/routes/packages/new.tsx | 2 +-
src/routes/plugins/index.tsx | 2 +-
src/routes/plugins/new.tsx | 2 +-
src/routes/plugins/publish.tsx | 422 ++++++++++
src/routes/publish-plugin.tsx | 380 +--------
src/routes/publish-skill.tsx | 739 +-----------------
src/routes/skills/publish.tsx | 738 +++++++++++++++++
src/routes/upload.tsx | 2 +-
49 files changed, 1943 insertions(+), 1160 deletions(-)
create mode 100644 convex/lib/publicRouteReservations.ts
create mode 100644 docs/publishing.md
create mode 100644 packages/schema/dist/docsLinks.d.ts
create mode 100644 packages/schema/dist/docsLinks.js
create mode 100644 packages/schema/dist/docsLinks.js.map
create mode 100644 packages/schema/src/docsLinks.ts
rename src/__tests__/{packages-publish-route.test.tsx => plugins-publish-route.test.tsx} (86%)
create mode 100644 src/__tests__/publish-redirect-routes.test.ts
rename src/__tests__/{upload.route.test.tsx => skills-publish-route.test.tsx} (99%)
create mode 100644 src/routes/plugins/publish.tsx
create mode 100644 src/routes/skills/publish.tsx
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c5de4d34..cba502fc 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -69,8 +69,10 @@
/src/routes/admin.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/cli/auth.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/packages/new.tsx @openclaw/openclaw-secops @BunsDev
+/src/routes/plugins/publish.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/publish-plugin.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/publish-skill.tsx @openclaw/openclaw-secops @BunsDev
+/src/routes/skills/publish.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/upload.tsx @openclaw/openclaw-secops @BunsDev
/src/routes/upload/ @openclaw/openclaw-secops @BunsDev
/src/routes/$owner/$slug/security/ @openclaw/openclaw-secops @BunsDev
diff --git a/.github/codeql/codeql-frontend-publish-security.yml b/.github/codeql/codeql-frontend-publish-security.yml
index 9b409bc7..9fd683eb 100644
--- a/.github/codeql/codeql-frontend-publish-security.yml
+++ b/.github/codeql/codeql-frontend-publish-security.yml
@@ -32,8 +32,10 @@ paths:
- src/routes/admin.tsx
- src/routes/cli/auth.tsx
- src/routes/packages/new.tsx
+ - src/routes/plugins/publish.tsx
- src/routes/publish-plugin.tsx
- src/routes/publish-skill.tsx
+ - src/routes/skills/publish.tsx
- src/routes/upload.tsx
- src/routes/upload
- src/routes/$owner/$slug/security
diff --git a/convex/lib/packageRegistry.test.ts b/convex/lib/packageRegistry.test.ts
index 0e818b6a..a3af5612 100644
--- a/convex/lib/packageRegistry.test.ts
+++ b/convex/lib/packageRegistry.test.ts
@@ -5,6 +5,7 @@ import {
ensurePluginNameMatchesPackage,
extractBundlePluginArtifacts,
extractCodePluginArtifacts,
+ normalizePackageName,
summarizePackageForSearch,
toConvexSafeJsonValue,
tryNormalizePackageName,
@@ -17,6 +18,11 @@ describe("packageRegistry", () => {
expect(tryNormalizePackageName(" ")).toBeNull();
});
+ it("reserves unscoped package names that collide with plugin routes", () => {
+ expect(() => normalizePackageName("publish")).toThrow("reserved for ClawHub routes");
+ expect(normalizePackageName("@demo/publish")).toBe("@demo/publish");
+ });
+
it("extracts code plugin compatibility and capabilities", () => {
const result = extractCodePluginArtifacts({
packageName: "@scope/demo-plugin",
diff --git a/convex/lib/packageRegistry.ts b/convex/lib/packageRegistry.ts
index 8fe07272..9f474f77 100644
--- a/convex/lib/packageRegistry.ts
+++ b/convex/lib/packageRegistry.ts
@@ -11,6 +11,10 @@ import type {
import { ConvexError } from "convex/values";
import semver from "semver";
import type { ActionCtx } from "../_generated/server";
+import {
+ formatReservedUnscopedPackageNameMessage,
+ isReservedUnscopedPackageName,
+} from "./publicRouteReservations";
import { getFrontmatterValue, parseFrontmatter, sanitizePath } from "./skills";
const PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
@@ -166,6 +170,9 @@ export function normalizePackageName(name: string) {
"Package name must be lowercase and npm-safe (example: @scope/name or plugin-name)",
);
}
+ if (!normalized.startsWith("@") && isReservedUnscopedPackageName(normalized)) {
+ throw new ConvexError(formatReservedUnscopedPackageNameMessage(normalized));
+ }
return normalized;
}
diff --git a/convex/lib/publicRouteReservations.ts b/convex/lib/publicRouteReservations.ts
new file mode 100644
index 00000000..5e503503
--- /dev/null
+++ b/convex/lib/publicRouteReservations.ts
@@ -0,0 +1,18 @@
+const RESERVED_PUBLIC_OWNER_HANDLES = new Set(["plugins", "skills"]);
+const RESERVED_UNSCOPED_PACKAGE_NAMES = new Set(["publish"]);
+
+export function isReservedPublicOwnerHandle(handle: string | undefined | null) {
+ return Boolean(handle && RESERVED_PUBLIC_OWNER_HANDLES.has(handle.trim().toLowerCase()));
+}
+
+export function isReservedUnscopedPackageName(name: string | undefined | null) {
+ return Boolean(name && RESERVED_UNSCOPED_PACKAGE_NAMES.has(name.trim().toLowerCase()));
+}
+
+export function formatReservedPublicOwnerHandleMessage(handle: string) {
+ return `Handle "@${handle}" is reserved for ClawHub routes. Choose a different handle.`;
+}
+
+export function formatReservedUnscopedPackageNameMessage(name: string) {
+ return `Package name "${name}" is reserved for ClawHub routes. Use a scoped name or choose a different package name.`;
+}
diff --git a/convex/packages.public.test.ts b/convex/packages.public.test.ts
index e8c38939..dd02ac5a 100644
--- a/convex/packages.public.test.ts
+++ b/convex/packages.public.test.ts
@@ -888,24 +888,64 @@ function makeInsertReleaseCtx(
existing: Record | null,
priorReleases: Array> = [],
recordsById: Record> = {},
+ runtimePackages: Array> = [],
) {
const patch = vi.fn();
- const insert = vi.fn().mockResolvedValueOnce("packageReleases:new");
+ let insertedPackage: Record | null = null;
+ const insert = vi.fn(async (table: string, doc: Record) => {
+ if (table === "packages") {
+ insertedPackage = makePackageDoc({
+ ...doc,
+ _id: "packages:new",
+ tags: {},
+ latestReleaseId: undefined,
+ latestVersionSummary: undefined,
+ stats: { downloads: 0, installs: 0, stars: 0, versions: 0 },
+ });
+ return "packages:new";
+ }
+ if (table === "packageReleases") return "packageReleases:new";
+ return `${table}:new`;
+ });
return {
patch,
insert,
db: {
get: vi.fn(async (id: string) => {
if (id in recordsById) return recordsById[id];
+ if (id === "packages:new") return insertedPackage;
if (id === "users:owner") return { _id: id, role: "user", trustedPublisher: false };
return null;
}),
query: vi.fn((table: string) => {
if (table === "packages") {
return {
- withIndex: vi.fn((_indexName: string) => ({
- unique: vi.fn().mockResolvedValue(existing),
- })),
+ withIndex: vi.fn(
+ (
+ indexName: string,
+ buildQuery?: (q: { eq: (field: string, value: unknown) => unknown }) => unknown,
+ ) => {
+ const filters = new Map();
+ const query = {
+ eq(field: string, value: unknown) {
+ filters.set(field, value);
+ return query;
+ },
+ };
+ buildQuery?.(query);
+ if (indexName === "by_runtime_id") {
+ const runtimeId = filters.get("runtimeId");
+ const matches = runtimePackages.filter((pkg) => pkg.runtimeId === runtimeId);
+ return {
+ collect: vi.fn().mockResolvedValue(matches),
+ unique: vi.fn().mockResolvedValue(matches[0] ?? null),
+ };
+ }
+ return {
+ unique: vi.fn().mockResolvedValue(existing),
+ };
+ },
+ ),
};
}
if (table === "packageReleases") {
@@ -1050,6 +1090,7 @@ function makeTransferPackageOwnerCtx(options?: {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(pkg),
+ collect: vi.fn().mockResolvedValue(pkg ? [pkg] : []),
})),
};
}),
@@ -2142,7 +2183,7 @@ describe("packages public queries", () => {
});
it("soft-deletes packages and active releases for the owner", async () => {
- const { ctx, patch } = makeSoftDeletePackageCtx({
+ const { ctx, insert, patch } = makeSoftDeletePackageCtx({
releases: [
makeReleaseDoc(),
makeReleaseDoc({
@@ -2182,6 +2223,25 @@ describe("packages public queries", () => {
updatedAt: expect.any(Number),
}),
);
+ expect(insert).toHaveBeenCalledWith(
+ "auditLogs",
+ expect.objectContaining({
+ actorUserId: "users:owner",
+ action: "package.delete",
+ targetType: "package",
+ targetId: "packages:demo",
+ metadata: expect.objectContaining({
+ name: "demo-plugin",
+ normalizedName: "demo-plugin",
+ ownerUserId: "users:owner",
+ ownerPublisherId: undefined,
+ releaseCount: 1,
+ releaseIds: ["packageReleases:demo-1"],
+ source: "cli",
+ }),
+ createdAt: expect.any(Number),
+ }),
+ );
});
it("rejects non-owner package soft deletes without moderator access", async () => {
@@ -2538,6 +2598,93 @@ describe("packages public queries", () => {
).rejects.toThrow("family changes are not allowed");
});
+ it("rejects new releases on a soft-deleted package", async () => {
+ const ctx = makeInsertReleaseCtx(makePackageDoc({ softDeletedAt: 123 }));
+
+ await expect(
+ insertReleaseInternalHandler(ctx, {
+ actorUserId: "users:owner",
+ ownerUserId: "users:owner",
+ name: "demo-plugin",
+ displayName: "Demo Plugin",
+ family: "code-plugin",
+ version: "1.0.1",
+ changelog: "try deleted package",
+ tags: ["latest"],
+ summary: "demo",
+ files: [],
+ integritySha256: "abc123",
+ }),
+ ).rejects.toThrow("Restore it before publishing another release");
+ });
+
+ it("rejects package scopes that do not match the selected owner handle", async () => {
+ const runMutation = vi.fn();
+ const ctx = {
+ runQuery: vi
+ .fn()
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce({
+ _id: "users:vintageayu",
+ githubCreatedAt: Date.now() - 20 * 24 * 60 * 60 * 1000,
+ })
+ .mockResolvedValueOnce({
+ _id: "users:vintageayu",
+ role: "user",
+ githubCreatedAt: Date.now() - 20 * 24 * 60 * 60 * 1000,
+ }),
+ runMutation,
+ scheduler: {
+ runAfter: vi.fn(),
+ },
+ storage: {
+ get: vi.fn(),
+ },
+ };
+
+ await expect(
+ publishPackageForUserInternalHandler(ctx as never, {
+ actorUserId: "users:vintageayu",
+ payload: {
+ name: "@openclaw/dronzer",
+ displayName: "Dronzer Controller",
+ ownerHandle: "vintageayu",
+ family: "code-plugin",
+ version: "1.0.0",
+ changelog: "init",
+ files: [],
+ },
+ }),
+ ).rejects.toThrow('Package scope "@openclaw" must match selected owner "@vintageayu"');
+
+ expect(runMutation).not.toHaveBeenCalled();
+ });
+
+ it("rejects unscoped package names that collide with publish routes", async () => {
+ const ctx = {
+ runQuery: vi.fn(),
+ runMutation: vi.fn(),
+ scheduler: { runAfter: vi.fn() },
+ storage: { get: vi.fn() },
+ };
+
+ await expect(
+ publishPackageForUserInternalHandler(ctx as never, {
+ actorUserId: "users:owner",
+ payload: {
+ name: "publish",
+ displayName: "Publish",
+ family: "code-plugin",
+ version: "1.0.0",
+ changelog: "init",
+ files: [],
+ },
+ }),
+ ).rejects.toThrow('Package name "publish" is reserved for ClawHub routes');
+
+ expect(ctx.runMutation).not.toHaveBeenCalled();
+ });
+
it("rejects runtime id changes on an existing code plugin package", async () => {
const ctx = makeInsertReleaseCtx(makePackageDoc({ runtimeId: "demo.plugin" }));
@@ -2559,6 +2706,64 @@ describe("packages public queries", () => {
).rejects.toThrow("runtime id changes are not allowed");
});
+ it("rejects plugin id collisions with active packages", async () => {
+ const ctx = makeInsertReleaseCtx(null, [], {}, [
+ makePackageDoc({
+ _id: "packages:claimed",
+ name: "claimed-plugin",
+ normalizedName: "claimed-plugin",
+ runtimeId: "dronzer",
+ softDeletedAt: undefined,
+ }),
+ ]);
+
+ await expect(
+ insertReleaseInternalHandler(ctx, {
+ actorUserId: "users:owner",
+ ownerUserId: "users:owner",
+ name: "dronzerclaw",
+ displayName: "Dronzer Claw",
+ family: "code-plugin",
+ version: "1.0.0",
+ changelog: "init",
+ tags: ["latest"],
+ summary: "demo",
+ files: [],
+ integritySha256: "abc123",
+ runtimeId: "dronzer",
+ }),
+ ).rejects.toThrow('Plugin id "dronzer" is already claimed by another package');
+ });
+
+ it("allows plugin ids held only by soft-deleted packages", async () => {
+ const ctx = makeInsertReleaseCtx(null, [], {}, [
+ makePackageDoc({
+ _id: "packages:deleted",
+ name: "@openclaw/dronzer",
+ normalizedName: "@openclaw/dronzer",
+ runtimeId: "dronzer",
+ softDeletedAt: 123,
+ }),
+ ]);
+
+ await expect(
+ insertReleaseInternalHandler(ctx, {
+ actorUserId: "users:owner",
+ ownerUserId: "users:owner",
+ name: "dronzerclaw",
+ displayName: "Dronzer Claw",
+ family: "code-plugin",
+ version: "1.0.0",
+ changelog: "init",
+ tags: ["latest"],
+ summary: "demo",
+ files: [],
+ integritySha256: "abc123",
+ runtimeId: "dronzer",
+ }),
+ ).resolves.toMatchObject({ ok: true, packageId: "packages:new" });
+ });
+
it("promotes existing packages to official when publisher becomes trusted", async () => {
const ctx = makeInsertReleaseCtx(
makePackageDoc({
diff --git a/convex/packages.ts b/convex/packages.ts
index f54b278c..2593ac1e 100644
--- a/convex/packages.ts
+++ b/convex/packages.ts
@@ -1,5 +1,6 @@
import {
PackagePublishRequestSchema,
+ getPackageScopeOwnerMismatch,
parseArk,
validateOpenClawExternalCodePluginPackageContents,
type PackageArtifactSummary,
@@ -2104,7 +2105,11 @@ export const insertAuditLogInternal = internalMutation({
},
});
-async function softDeletePackageDoc(ctx: Pick, pkg: Doc<"packages">) {
+async function softDeletePackageDoc(
+ ctx: Pick,
+ pkg: Doc<"packages">,
+ params: { actorUserId: Id<"users">; source: "cli" | "dashboard" },
+) {
if (pkg.softDeletedAt) {
return {
ok: true as const,
@@ -2120,10 +2125,12 @@ async function softDeletePackageDoc(ctx: Pick, pkg: Doc<"pack
.withIndex("by_package", (q) => q.eq("packageId", pkg._id))
.collect();
let releaseCount = 0;
+ const deletedReleaseIds: Array> = [];
for (const release of releases) {
if (release.softDeletedAt) continue;
await ctx.db.patch(release._id, { softDeletedAt: now });
releaseCount += 1;
+ deletedReleaseIds.push(release._id);
}
const packagePatch = {
@@ -2135,6 +2142,22 @@ async function softDeletePackageDoc(ctx: Pick, pkg: Doc<"pack
...extractPackageDigestFields(pkg),
...packagePatch,
});
+ await ctx.db.insert("auditLogs", {
+ actorUserId: params.actorUserId,
+ action: "package.delete",
+ targetType: "package",
+ targetId: pkg._id,
+ metadata: {
+ name: pkg.name,
+ normalizedName: pkg.normalizedName,
+ ownerUserId: pkg.ownerUserId,
+ ownerPublisherId: pkg.ownerPublisherId,
+ releaseCount,
+ releaseIds: deletedReleaseIds,
+ source: params.source,
+ },
+ createdAt: now,
+ });
return {
ok: true as const,
@@ -2170,7 +2193,7 @@ export const softDeletePackageInternal = internalMutation({
});
}
- return await softDeletePackageDoc(ctx, pkg);
+ return await softDeletePackageDoc(ctx, pkg, { actorUserId: user._id, source: "cli" });
},
});
@@ -2194,7 +2217,7 @@ export const softDeletePackage = mutation({
});
}
- return await softDeletePackageDoc(ctx, pkg);
+ return await softDeletePackageDoc(ctx, pkg, { actorUserId: user._id, source: "dashboard" });
},
});
@@ -3620,6 +3643,8 @@ async function publishPackageImpl(
const actor = await runQueryRef | null>(ctx, internalRefs.users.getByIdInternal, {
userId: actorUserId,
});
+ const ownerMismatch = getPackageScopeOwnerMismatch(name, payload.ownerHandle);
+ if (ownerMismatch) throw new ConvexError(ownerMismatch.message);
const ownerHandle = payload.ownerHandle ?? inferOwnerHandleFromScopedPackageName(name);
const ownerTarget = await runMutationRef<{
publisherId: Id<"publishers">;
@@ -4164,11 +4189,14 @@ export const repairPackageIdentityInternal = internalMutation({
if (typeof args.nextRuntimeId === "string") {
const nextRuntimeId = args.nextRuntimeId.trim();
if (!nextRuntimeId) throw new ConvexError("Runtime id required");
- const runtimeCollision = await ctx.db
+ const runtimeCollisions = await ctx.db
.query("packages")
.withIndex("by_runtime_id", (q) => q.eq("runtimeId", nextRuntimeId))
- .unique();
- if (runtimeCollision && runtimeCollision._id !== pkg._id && !runtimeCollision.softDeletedAt) {
+ .collect();
+ const runtimeCollision = runtimeCollisions.find(
+ (candidate) => candidate._id !== pkg._id && !candidate.softDeletedAt,
+ );
+ if (runtimeCollision) {
throw new ConvexError(`Plugin id "${nextRuntimeId}" is already claimed by another package`);
}
patch.runtimeId = nextRuntimeId;
@@ -4277,6 +4305,12 @@ export const insertReleaseInternal = internalMutation({
const nextCapabilityTags = mergeArtifactCapabilityTags(args.capabilities?.capabilityTags, args);
const existing = await getPackageByNormalizedName(ctx, normalizedName);
const existingIsReservation = isReservedPackagePlaceholder(existing);
+ const nextNameLabel = typeof args.name === "string" ? args.name : "";
+ if (existing?.softDeletedAt) {
+ throw new ConvexError(
+ `Package "${nextNameLabel}" was deleted. Restore it before publishing another release or choose a new package name.`,
+ );
+ }
const nextChannel =
args.channel ??
(existing?.channel === "private" && !existingIsReservation
@@ -4285,7 +4319,6 @@ export const insertReleaseInternal = internalMutation({
? "official"
: "community");
const nextIsOfficial = nextChannel === "official";
- const nextNameLabel = typeof args.name === "string" ? args.name : "";
const nextRuntimeIdLabel = typeof args.runtimeId === "string" ? args.runtimeId : "";
const nextVersionLabel = typeof args.version === "string" ? args.version : "";
if (existing) {
@@ -4318,11 +4351,14 @@ export const insertReleaseInternal = internalMutation({
);
}
if (args.family === "code-plugin" && args.runtimeId) {
- const runtimeCollision = await ctx.db
+ const runtimeCollisions = await ctx.db
.query("packages")
.withIndex("by_runtime_id", (q) => q.eq("runtimeId", args.runtimeId))
- .unique();
- if (runtimeCollision && runtimeCollision._id !== existing?._id) {
+ .collect();
+ const runtimeCollision = runtimeCollisions.find(
+ (candidate) => candidate._id !== existing?._id && !candidate.softDeletedAt,
+ );
+ if (runtimeCollision) {
throw new ConvexError(
`Plugin id "${nextRuntimeIdLabel}" is already claimed by another package`,
);
diff --git a/convex/publishers.test.ts b/convex/publishers.test.ts
index e4e67cc4..6ced919a 100644
--- a/convex/publishers.test.ts
+++ b/convex/publishers.test.ts
@@ -63,6 +63,27 @@ const updateProfileHandler = (
)._handler;
describe("publishers membership controls", () => {
+ it("rejects org handles reserved for public routes", async () => {
+ const ctx = {
+ db: {
+ get: vi.fn(async (id: string) => (id === "users:admin" ? { _id: id, role: "admin" } : null)),
+ query: vi.fn(),
+ insert: vi.fn(),
+ patch: vi.fn(),
+ delete: vi.fn(),
+ replace: vi.fn(),
+ normalizeId: vi.fn(),
+ },
+ };
+
+ await expect(
+ migrateLegacyPublisherHandleToOrgInternalHandler(ctx, {
+ actorUserId: "users:admin",
+ handle: "skills",
+ }),
+ ).rejects.toThrow('Handle "@skills" is reserved for ClawHub routes');
+ });
+
it("prevents admins from promoting members to owner", async () => {
vi.mocked(getAuthUserId).mockResolvedValue("users:admin" as never);
const ctx = {
diff --git a/convex/publishers.ts b/convex/publishers.ts
index e934368a..1a2d78af 100644
--- a/convex/publishers.ts
+++ b/convex/publishers.ts
@@ -14,6 +14,10 @@ import {
isPublisherRoleAllowed,
normalizePublisherHandle,
} from "./lib/publishers";
+import {
+ formatReservedPublicOwnerHandleMessage,
+ isReservedPublicOwnerHandle,
+} from "./lib/publicRouteReservations";
const PUBLISHER_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
@@ -23,6 +27,9 @@ function validateHandle(rawHandle: string) {
if (!PUBLISHER_HANDLE_PATTERN.test(handle)) {
throw new ConvexError("Handle must be lowercase, url-safe, and 2-40 characters");
}
+ if (isReservedPublicOwnerHandle(handle)) {
+ throw new ConvexError(formatReservedPublicOwnerHandleMessage(handle));
+ }
return handle;
}
diff --git a/convex/users.test.ts b/convex/users.test.ts
index 37c9c462..22316086 100644
--- a/convex/users.test.ts
+++ b/convex/users.test.ts
@@ -389,6 +389,30 @@ describe("ensureHandler", () => {
});
});
+ it("skips public route owner handles when deriving a handle", async () => {
+ const { ctx, patch } = makeCtx();
+ vi.mocked(requireUser).mockResolvedValue({
+ userId: "users:skills",
+ user: {
+ _creationTime: 1,
+ handle: undefined,
+ displayName: undefined,
+ name: "skills",
+ email: undefined,
+ role: "user",
+ createdAt: 1,
+ },
+ } as never);
+
+ await ensureHandler(ctx);
+
+ expect(patch).toHaveBeenCalledWith("users:skills", {
+ handle: "skills-2",
+ displayName: "skills-2",
+ updatedAt: expect.any(Number),
+ });
+ });
+
it("repairs an existing handle that is no longer claimable", async () => {
const { ctx, patch, query } = makeCtx();
query.mockImplementation(((table: string) => {
diff --git a/convex/users.ts b/convex/users.ts
index 9d31b237..e6938d78 100644
--- a/convex/users.ts
+++ b/convex/users.ts
@@ -23,6 +23,7 @@ import {
normalizeReservedHandle,
upsertReservedHandleForRightfulOwner,
} from "./lib/reservedHandles";
+import { isReservedPublicOwnerHandle } from "./lib/publicRouteReservations";
import { buildUserSearchResults } from "./lib/userSearch";
import { insertStatEvent } from "./skillStatEvents";
@@ -233,6 +234,7 @@ async function canUserClaimHandle(
) {
const normalizedHandle = normalizeReservedHandle(handle);
if (!normalizedHandle) return false;
+ if (isReservedPublicOwnerHandle(normalizedHandle)) return false;
if (await isHandleReservedForAnotherUser(ctx, normalizedHandle, userId)) return false;
const publisher = await getPublisherByHandle(ctx, normalizedHandle);
diff --git a/docs/README.md b/docs/README.md
index 32d68e3a..2021b16e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -25,12 +25,13 @@ Reading order:
1. `docs/clawhub.md`: public overview for discovery, install, publish, and trust.
2. `docs/quickstart.md`: product quickstart for users and publishers.
3. `docs/how-it-works.md`: listings, versions, installs, publishing, scans, and API access.
-4. `docs/cli.md`: ClawHub CLI reference.
-5. `docs/skill-format.md`: skill bundle metadata and package shape.
-6. `docs/soul-format.md`: SOUL.md bundle format.
-7. `docs/auth.md`: GitHub OAuth, API tokens, and CLI login.
-8. `docs/telemetry.md`: what `clawhub sync` reports and how to opt out.
-9. `docs/troubleshooting.md`: user-facing CLI, install, publish, sync, update, and API fixes.
+4. `docs/publishing.md`: owner-scoped skill/plugin publishing flow.
+5. `docs/cli.md`: ClawHub CLI reference.
+6. `docs/skill-format.md`: skill bundle metadata and package shape.
+7. `docs/soul-format.md`: SOUL.md bundle format.
+8. `docs/auth.md`: GitHub OAuth, API tokens, and CLI login.
+9. `docs/telemetry.md`: what `clawhub sync` reports and how to opt out.
+10. `docs/troubleshooting.md`: user-facing CLI, install, publish, sync, update, and API fixes.
Policy, API, and trust docs:
diff --git a/docs/cli.md b/docs/cli.md
index 8fab4878..2704a008 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -154,6 +154,7 @@ Stores your API token + cached registry URL.
- Requires semver: `--version 1.2.3`.
- `--owner ` publishes under an org/user publisher handle when the
actor has publisher access.
+- Owner and review behavior is explained in `docs/publishing.md`.
- Publishing a skill means it is released under `MIT-0` on ClawHub.
- Published skills are free to use, modify, and redistribute without attribution.
- ClawHub does not support paid skills or per-skill pricing.
@@ -421,6 +422,7 @@ clawhub package migration-status @openclaw/example-plugin
- `--dry-run` previews the resolved publish payload without uploading.
- `--json` emits machine-readable output for CI.
- `--owner ` publishes under a user or org publisher handle when the actor has publisher access.
+- Scoped package names must match the selected owner. See `docs/publishing.md`.
- Existing flags (`--family`, `--name`, `--version`, `--source-repo`, `--source-commit`, `--source-ref`, `--source-path`) still work as overrides.
- Private GitHub repos require `GITHUB_TOKEN`.
diff --git a/docs/publishing.md b/docs/publishing.md
new file mode 100644
index 00000000..76a09dcf
--- /dev/null
+++ b/docs/publishing.md
@@ -0,0 +1,85 @@
+---
+summary: "How ClawHub publishing works for skills, plugins, owners, scopes, releases, and review."
+read_when:
+ - Publishing a skill or plugin
+ - Debugging owner or package scope errors
+ - Adding publish UI, CLI, or backend behavior
+---
+
+# Publishing
+
+ClawHub publishing is owner-scoped: every publish targets a publisher, and the
+server decides whether the signed-in user is allowed to publish there.
+
+## Owners
+
+An owner is a ClawHub publisher handle, such as `@alice` or `@openclaw`.
+Personal owners are created for users. Org owners can have multiple members.
+
+When you publish, you either use your personal owner or choose an org owner
+where you have publisher access.
+
+## Skills
+
+Skills are published from a skill folder. The public page is:
+
+```text
+https://clawhub.ai//
+```
+
+Example:
+
+```text
+https://clawhub.ai/alice/review-helper
+```
+
+The publish request includes the selected owner, slug, version, changelog, and
+files. The server verifies that the actor can publish as that owner before it
+creates the release.
+
+## Plugins
+
+Plugins use npm-style package names. Scoped package names include the owner in
+the first part of the name:
+
+```text
+@owner/package-name
+```
+
+The scope must match the selected publish owner. If your package is named
+`@openclaw/dronzer`, it can only be published as `@openclaw`. If you publish as
+`@vintageayu`, rename the package to `@vintageayu/dronzer`.
+
+This prevents a package from claiming an org namespace that the publisher does
+not control.
+
+## Release Flow
+
+1. The UI, CLI, or GitHub workflow gathers package metadata and files.
+2. The publish request is sent to ClawHub with the selected owner.
+3. The server validates owner permissions, package scope, package name, version,
+ file limits, and source metadata.
+4. ClawHub stores the release and starts automated security checks.
+5. New releases are hidden from normal install/download surfaces until review
+ and verification finish.
+
+If validation fails, the release is not created.
+
+## FAQ
+
+### Why does the package scope need to match the selected owner?
+
+If the package scope and selected owner do not match, ClawHub rejects the
+publish:
+
+```text
+Package scope "@openclaw" must match selected owner "@vintageayu".
+Publish as "@openclaw" or rename this package to "@vintageayu/dronzer".
+```
+
+To fix it, either choose the owner named by the package scope, or rename the
+package so the scope matches the owner you can publish as.
+
+This protects org namespaces. A package named `@openclaw/dronzer` claims the
+`@openclaw` namespace, so only publishers with access to the `@openclaw` owner
+can publish it.
diff --git a/e2e/ci-smoke.pw.test.ts b/e2e/ci-smoke.pw.test.ts
index e2a13c57..6bf20cae 100644
--- a/e2e/ci-smoke.pw.test.ts
+++ b/e2e/ci-smoke.pw.test.ts
@@ -27,7 +27,7 @@ test("signed-out publish entry renders", async ({ page }) => {
const errors = trackRuntimeErrors(page);
await page.goto("/upload", { waitUntil: "domcontentloaded" });
- await expect(page).toHaveURL(/\/publish-skill$/);
+ await expect(page).toHaveURL(/\/skills\/publish$/);
await expect(page.getByText("Sign in to publish a skill")).toBeVisible();
await expectHealthyPage(page, errors);
});
diff --git a/e2e/prod-http-smoke.e2e.test.ts b/e2e/prod-http-smoke.e2e.test.ts
index 558caf55..5f12c2af 100644
--- a/e2e/prod-http-smoke.e2e.test.ts
+++ b/e2e/prod-http-smoke.e2e.test.ts
@@ -113,7 +113,7 @@ describe("prod http smoke", () => {
expect(html).toContain("ClawHub");
expect(html).toContain('href="/skills"');
- expect(html).toContain('href="/publish-skill"');
+ expect(html).toMatch(/href="\/(?:skills\/publish|publish-skill)"/);
expect(html).not.toContain("Something went wrong!");
});
diff --git a/e2e/publish-entry-workflows.pw.test.ts b/e2e/publish-entry-workflows.pw.test.ts
index 807c1671..e71f4ff2 100644
--- a/e2e/publish-entry-workflows.pw.test.ts
+++ b/e2e/publish-entry-workflows.pw.test.ts
@@ -5,7 +5,7 @@ test("upload shows signed-out publish gate", async ({ page }) => {
const errors = trackRuntimeErrors(page);
await page.goto("/upload", { waitUntil: "domcontentloaded" });
- await expect(page).toHaveURL(/\/publish-skill$/);
+ await expect(page).toHaveURL(/\/skills\/publish$/);
await expect(page.getByText("Sign in to publish a skill")).toBeVisible();
await expectHealthyPage(page, errors);
});
diff --git a/packages/schema/dist/docsLinks.d.ts b/packages/schema/dist/docsLinks.d.ts
new file mode 100644
index 00000000..981ed151
--- /dev/null
+++ b/packages/schema/dist/docsLinks.d.ts
@@ -0,0 +1,12 @@
+export declare const OPENCLAW_DOCS_BASE_URL = "https://docs.openclaw.ai";
+export declare function openClawDocsUrl(path: string): string;
+export declare const DocsLinks: {
+ readonly clawhub: {
+ readonly acceptableUsage: string;
+ readonly publishing: string;
+ readonly packageScopeFaq: string;
+ };
+ readonly openclaw: {
+ readonly pluginPackageMetadata: string;
+ };
+};
diff --git a/packages/schema/dist/docsLinks.js b/packages/schema/dist/docsLinks.js
new file mode 100644
index 00000000..2b216130
--- /dev/null
+++ b/packages/schema/dist/docsLinks.js
@@ -0,0 +1,16 @@
+export const OPENCLAW_DOCS_BASE_URL = "https://docs.openclaw.ai";
+export function openClawDocsUrl(path) {
+ const trimmed = path.trim().replace(/^\/+/, "");
+ return new URL(trimmed, `${OPENCLAW_DOCS_BASE_URL}/`).href;
+}
+export const DocsLinks = {
+ clawhub: {
+ acceptableUsage: openClawDocsUrl("clawhub/acceptable-usage"),
+ publishing: openClawDocsUrl("clawhub/publishing"),
+ packageScopeFaq: openClawDocsUrl("clawhub/publishing#why-does-the-package-scope-need-to-match-the-selected-owner"),
+ },
+ openclaw: {
+ pluginPackageMetadata: openClawDocsUrl("plugins/sdk-setup#package-metadata"),
+ },
+};
+//# sourceMappingURL=docsLinks.js.map
\ No newline at end of file
diff --git a/packages/schema/dist/docsLinks.js.map b/packages/schema/dist/docsLinks.js.map
new file mode 100644
index 00000000..37f63e7b
--- /dev/null
+++ b/packages/schema/dist/docsLinks.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"docsLinks.js","sourceRoot":"","sources":["../src/docsLinks.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAEjE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChD,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE;QACP,eAAe,EAAE,eAAe,CAAC,0BAA0B,CAAC;QAC5D,UAAU,EAAE,eAAe,CAAC,oBAAoB,CAAC;QACjD,eAAe,EAAE,eAAe,CAC9B,gFAAgF,CACjF;KACF;IACD,QAAQ,EAAE;QACR,qBAAqB,EAAE,eAAe,CAAC,oCAAoC,CAAC;KAC7E;CACO,CAAC"}
\ No newline at end of file
diff --git a/packages/schema/dist/index.d.ts b/packages/schema/dist/index.d.ts
index c40af08e..1fae4c7f 100644
--- a/packages/schema/dist/index.d.ts
+++ b/packages/schema/dist/index.d.ts
@@ -1,5 +1,6 @@
export type { ArkValidator } from "./ark.js";
export { formatArkErrors, parseArk } from "./ark.js";
+export * from "./docsLinks.js";
export * from "./license.js";
export * from "./openclawContract.js";
export * from "./packages.js";
diff --git a/packages/schema/dist/index.js b/packages/schema/dist/index.js
index e1758269..53e777c8 100644
--- a/packages/schema/dist/index.js
+++ b/packages/schema/dist/index.js
@@ -1,4 +1,5 @@
export { formatArkErrors, parseArk } from "./ark.js";
+export * from "./docsLinks.js";
export * from "./license.js";
export * from "./openclawContract.js";
export * from "./packages.js";
diff --git a/packages/schema/dist/index.js.map b/packages/schema/dist/index.js.map
index a2517162..dc7b522b 100644
--- a/packages/schema/dist/index.js.map
+++ b/packages/schema/dist/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
\ No newline at end of file
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
\ No newline at end of file
diff --git a/packages/schema/dist/packages.d.ts b/packages/schema/dist/packages.d.ts
index 5cd0f3bd..b1e85340 100644
--- a/packages/schema/dist/packages.d.ts
+++ b/packages/schema/dist/packages.d.ts
@@ -1,4 +1,12 @@
import { type inferred } from "arktype";
+export declare function normalizePackageOwnerHandle(handle: string | null | undefined): string | undefined;
+export declare function inferPackageNameScope(name: string): string | undefined;
+export declare function getPackageScopeOwnerMismatch(name: string, ownerHandle: string | null | undefined): {
+ scope: string;
+ selectedOwner: string;
+ suggestedName: string;
+ message: string;
+} | null;
export declare const PackageFamilySchema: import("arktype/internal/variants/string.ts").StringType<"skill" | "code-plugin" | "bundle-plugin", {}>;
export type PackageFamily = (typeof PackageFamilySchema)[inferred];
export declare const PackageChannelSchema: import("arktype/internal/variants/string.ts").StringType<"official" | "community" | "private", {}>;
diff --git a/packages/schema/dist/packages.js b/packages/schema/dist/packages.js
index 316c3e07..50a72dc6 100644
--- a/packages/schema/dist/packages.js
+++ b/packages/schema/dist/packages.js
@@ -1,5 +1,26 @@
import { type } from "arktype";
+import { DocsLinks } from "./docsLinks.js";
import { CliPublishFileSchema, PublishSourceSchema } from "./schemas.js";
+export function normalizePackageOwnerHandle(handle) {
+ const normalized = handle?.trim().replace(/^@+/, "").toLowerCase();
+ return normalized || undefined;
+}
+export function inferPackageNameScope(name) {
+ return /^@([^/]+)\//.exec(name.trim().toLowerCase())?.[1];
+}
+export function getPackageScopeOwnerMismatch(name, ownerHandle) {
+ const scope = inferPackageNameScope(name);
+ const selectedOwner = normalizePackageOwnerHandle(ownerHandle);
+ if (!scope || !selectedOwner || scope === selectedOwner)
+ return null;
+ const packageSlug = name.split("/").pop()?.trim() || "plugin-name";
+ return {
+ scope,
+ selectedOwner,
+ suggestedName: `@${selectedOwner}/${packageSlug}`,
+ message: `Package scope "@${scope}" must match selected owner "@${selectedOwner}". Publish as "@${scope}" or rename this package to "@${selectedOwner}/${packageSlug}". More info: ${DocsLinks.clawhub.packageScopeFaq}`,
+ };
+}
export const PackageFamilySchema = type('"skill"|"code-plugin"|"bundle-plugin"');
export const PackageChannelSchema = type('"official"|"community"|"private"');
export const PackageVerificationTierSchema = type('"structural"|"source-linked"|"provenance-verified"|"rebuild-verified"');
diff --git a/packages/schema/dist/packages.js.map b/packages/schema/dist/packages.js.map
index fc9aea49..eb709bc0 100644
--- a/packages/schema/dist/packages.js.map
+++ b/packages/schema/dist/packages.js.map
@@ -1 +1 @@
-{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/schema/src/docsLinks.ts b/packages/schema/src/docsLinks.ts
new file mode 100644
index 00000000..b201e744
--- /dev/null
+++ b/packages/schema/src/docsLinks.ts
@@ -0,0 +1,19 @@
+export const OPENCLAW_DOCS_BASE_URL = "https://docs.openclaw.ai";
+
+export function openClawDocsUrl(path: string) {
+ const trimmed = path.trim().replace(/^\/+/, "");
+ return new URL(trimmed, `${OPENCLAW_DOCS_BASE_URL}/`).href;
+}
+
+export const DocsLinks = {
+ clawhub: {
+ acceptableUsage: openClawDocsUrl("clawhub/acceptable-usage"),
+ publishing: openClawDocsUrl("clawhub/publishing"),
+ packageScopeFaq: openClawDocsUrl(
+ "clawhub/publishing#why-does-the-package-scope-need-to-match-the-selected-owner",
+ ),
+ },
+ openclaw: {
+ pluginPackageMetadata: openClawDocsUrl("plugins/sdk-setup#package-metadata"),
+ },
+} as const;
diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts
index c40af08e..1fae4c7f 100644
--- a/packages/schema/src/index.ts
+++ b/packages/schema/src/index.ts
@@ -1,5 +1,6 @@
export type { ArkValidator } from "./ark.js";
export { formatArkErrors, parseArk } from "./ark.js";
+export * from "./docsLinks.js";
export * from "./license.js";
export * from "./openclawContract.js";
export * from "./packages.js";
diff --git a/packages/schema/src/packages.ts b/packages/schema/src/packages.ts
index 2c0abb05..1121d9ac 100644
--- a/packages/schema/src/packages.ts
+++ b/packages/schema/src/packages.ts
@@ -1,6 +1,29 @@
import { type inferred, type } from "arktype";
+import { DocsLinks } from "./docsLinks.js";
import { CliPublishFileSchema, PublishSourceSchema } from "./schemas.js";
+export function normalizePackageOwnerHandle(handle: string | null | undefined) {
+ const normalized = handle?.trim().replace(/^@+/, "").toLowerCase();
+ return normalized || undefined;
+}
+
+export function inferPackageNameScope(name: string) {
+ return /^@([^/]+)\//.exec(name.trim().toLowerCase())?.[1];
+}
+
+export function getPackageScopeOwnerMismatch(name: string, ownerHandle: string | null | undefined) {
+ const scope = inferPackageNameScope(name);
+ const selectedOwner = normalizePackageOwnerHandle(ownerHandle);
+ if (!scope || !selectedOwner || scope === selectedOwner) return null;
+ const packageSlug = name.split("/").pop()?.trim() || "plugin-name";
+ return {
+ scope,
+ selectedOwner,
+ suggestedName: `@${selectedOwner}/${packageSlug}`,
+ message: `Package scope "@${scope}" must match selected owner "@${selectedOwner}". Publish as "@${scope}" or rename this package to "@${selectedOwner}/${packageSlug}". More info: ${DocsLinks.clawhub.packageScopeFaq}`,
+ };
+}
+
export const PackageFamilySchema = type('"skill"|"code-plugin"|"bundle-plugin"');
export type PackageFamily = (typeof PackageFamilySchema)[inferred];
diff --git a/packages/schema/src/schemas.test.ts b/packages/schema/src/schemas.test.ts
index 1c327c47..ad58f34a 100644
--- a/packages/schema/src/schemas.test.ts
+++ b/packages/schema/src/schemas.test.ts
@@ -2,6 +2,8 @@
import { describe, expect, it } from "vitest";
import { parseArk } from "./ark";
+import { DocsLinks, openClawDocsUrl } from "./docsLinks";
+import { getPackageScopeOwnerMismatch, inferPackageNameScope } from "./packages";
import {
ApiSearchResponseSchema,
CliPublishRequestSchema,
@@ -76,6 +78,30 @@ describe("clawhub-schema", () => {
expect(payload.ownerHandle).toBe("openclaw");
});
+ it("reports scoped package names that do not match the selected owner", () => {
+ expect(inferPackageNameScope("@openclaw/dronzer")).toBe("openclaw");
+ expect(getPackageScopeOwnerMismatch("@openclaw/dronzer", "openclaw")).toBeNull();
+ expect(getPackageScopeOwnerMismatch("@openclaw/dronzer", "@VintageAyu")).toEqual({
+ scope: "openclaw",
+ selectedOwner: "vintageayu",
+ suggestedName: "@vintageayu/dronzer",
+ message:
+ `Package scope "@openclaw" must match selected owner "@vintageayu". Publish as "@openclaw" or rename this package to "@vintageayu/dronzer". More info: ${DocsLinks.clawhub.packageScopeFaq}`,
+ });
+ });
+
+ it("builds OpenClaw docs URLs from normalized paths", () => {
+ expect(openClawDocsUrl("/clawhub/publishing")).toBe(DocsLinks.clawhub.publishing);
+ expect(
+ openClawDocsUrl(
+ "clawhub/publishing#why-does-the-package-scope-need-to-match-the-selected-owner",
+ ),
+ ).toBe(DocsLinks.clawhub.packageScopeFaq);
+ expect(openClawDocsUrl("plugins/sdk-setup#package-metadata")).toBe(
+ DocsLinks.openclaw.pluginPackageMetadata,
+ );
+ });
+
it("parses well-known config", () => {
expect(
parseArk(WellKnownConfigSchema, { registry: "https://example.convex.site" }, "WellKnown"),
diff --git a/src/__tests__/packages-publish-route.test.tsx b/src/__tests__/plugins-publish-route.test.tsx
similarity index 86%
rename from src/__tests__/packages-publish-route.test.tsx
rename to src/__tests__/plugins-publish-route.test.tsx
index bed3fdc0..6467464d 100644
--- a/src/__tests__/packages-publish-route.test.tsx
+++ b/src/__tests__/plugins-publish-route.test.tsx
@@ -1,6 +1,7 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { DocsLinks } from "clawhub-schema";
import { createElement } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -19,24 +20,29 @@ vi.mock("@tanstack/react-router", () => ({
}),
}));
+vi.mock("@convex-dev/auth/react", () => ({
+ useAuthActions: () => ({ signIn: vi.fn() }),
+}));
+
const generateUploadUrl = vi.fn();
const publishRelease = vi.fn();
const fetchMock = vi.fn();
const useAuthStatusMock = vi.fn();
+const useQueryMock = vi.fn();
const originalFetch = globalThis.fetch;
vi.mock("convex/react", () => ({
ConvexReactClient: class {},
useMutation: () => generateUploadUrl,
useAction: () => publishRelease,
- useQuery: () => undefined,
+ useQuery: () => useQueryMock(),
}));
vi.mock("../lib/useAuthStatus", () => ({
useAuthStatus: () => useAuthStatusMock(),
}));
-import { PublishPluginRoute, Route } from "../routes/publish-plugin";
+import { PublishPluginRoute, Route } from "../routes/plugins/publish";
function renderPublishRoute() {
render(createElement(PublishPluginRoute as never));
@@ -81,12 +87,24 @@ describe("plugins publish route", () => {
publishRelease.mockReset();
fetchMock.mockReset();
useAuthStatusMock.mockReset();
+ useQueryMock.mockReset();
useAuthStatusMock.mockReturnValue({
isAuthenticated: true,
isLoading: false,
me: { _id: "users:1" },
});
+ useQueryMock.mockReturnValue([
+ {
+ publisher: {
+ _id: "publishers:vintageayu",
+ handle: "vintageayu",
+ displayName: "VintageAyu",
+ kind: "user",
+ },
+ role: "owner",
+ },
+ ]);
generateUploadUrl.mockResolvedValue("https://upload.local");
publishRelease.mockResolvedValue({ ok: true, packageId: "pkg:1", releaseId: "rel:1" });
fetchMock.mockImplementation(async (_url: string, init?: RequestInit) => ({
@@ -110,10 +128,27 @@ describe("plugins publish route", () => {
});
});
- it("registers the publish form on /publish-plugin", () => {
+ it("registers the publish form on /plugins/publish", () => {
expect(Route).toBeTruthy();
});
+ it("requires sign-in before showing the plugin publish form", () => {
+ useAuthStatusMock.mockReturnValue({
+ isAuthenticated: false,
+ isLoading: false,
+ me: null,
+ });
+
+ renderPublishRoute();
+
+ expect(screen.getByText("Sign in to publish a plugin")).toBeTruthy();
+ expect(
+ screen.getByText("You need to be signed in to publish plugins on ClawHub."),
+ ).toBeTruthy();
+ expect(screen.queryByText(/Upload plugin code to detect the package shape/i)).toBeNull();
+ expect(screen.queryByPlaceholderText("Plugin name")).toBeNull();
+ });
+
it("keeps metadata inputs locked until plugin code is uploaded", () => {
renderPublishRoute();
@@ -278,9 +313,47 @@ describe("plugins publish route", () => {
expect(screen.getByText(/openclaw\.compat\.pluginApi/i)).toBeTruthy();
expect(screen.getByText(/openclaw\.build\.openclawVersion/i)).toBeTruthy();
const docsLink = screen.getByRole("link", { name: /Plugin Setup and Config/i });
- expect(docsLink.getAttribute("href")).toBe(
- "https://docs.openclaw.ai/plugins/sdk-setup#package-metadata",
+ expect(docsLink.getAttribute("href")).toBe(DocsLinks.openclaw.pluginPackageMetadata);
+ expect(docsLink.getAttribute("target")).toBe("_blank");
+ expect(docsLink.getAttribute("rel")).toBe("noopener noreferrer");
+ expect(screen.getByRole("button", { name: "Publish" }).getAttribute("disabled")).not.toBeNull();
+ expect(publishRelease).not.toHaveBeenCalled();
+ });
+
+ it("blocks scoped package names that do not match the selected owner", async () => {
+ renderPublishRoute();
+
+ const packageJson = withRelativePath(
+ new File(
+ [
+ makeCodePluginPackageJson({
+ name: "@openclaw/dronzer",
+ displayName: "Dronzer Controller",
+ version: "1.0.0",
+ repository: "https://github.com/VintageAyu/dronzerclaw.git",
+ }),
+ ],
+ "package.json",
+ { type: "application/json" },
+ ),
+ "dronzer/package.json",
);
+ const manifest = withRelativePath(
+ new File(['{"id":"dronzer"}'], "openclaw.plugin.json", { type: "application/json" }),
+ "dronzer/openclaw.plugin.json",
+ );
+
+ fireEvent.change(getFileInput(), { target: { files: [packageJson, manifest] } });
+
+ await waitFor(() => {
+ expect(screen.getByDisplayValue("@openclaw/dronzer")).toBeTruthy();
+ expect(
+ screen.getByText(/Package scope "@openclaw" must match selected owner "@vintageayu"/i),
+ ).toBeTruthy();
+ });
+
+ const docsLink = screen.getByRole("link", { name: /Learn how publishing works/i });
+ expect(docsLink.getAttribute("href")).toBe(DocsLinks.clawhub.packageScopeFaq);
expect(docsLink.getAttribute("target")).toBe("_blank");
expect(docsLink.getAttribute("rel")).toBe("noopener noreferrer");
expect(screen.getByRole("button", { name: "Publish" }).getAttribute("disabled")).not.toBeNull();
diff --git a/src/__tests__/publish-redirect-routes.test.ts b/src/__tests__/publish-redirect-routes.test.ts
new file mode 100644
index 00000000..1e94f38c
--- /dev/null
+++ b/src/__tests__/publish-redirect-routes.test.ts
@@ -0,0 +1,58 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const redirectMock = vi.fn((options: unknown) => ({ redirect: options }));
+
+vi.mock("@tanstack/react-router", () => ({
+ createFileRoute: (path: string) => (config: unknown) => ({ __config: config, __path: path }),
+ redirect: (options: unknown) => redirectMock(options),
+}));
+
+type LegacyRedirectRoute = {
+ __config: {
+ beforeLoad: (args: { search: Record }) => never;
+ validateSearch: (search: Record) => Record;
+ };
+ __path: string;
+};
+
+async function loadRoute(path: string): Promise {
+ return ((await import(path)) as { Route: LegacyRedirectRoute }).Route;
+}
+
+describe("legacy publish redirects", () => {
+ beforeEach(() => {
+ redirectMock.mockClear();
+ });
+
+ it("redirects legacy plugin publish links to /plugins/publish", async () => {
+ const route = await loadRoute("../routes/publish-plugin");
+ const search = route.__config.validateSearch({
+ displayName: "Dronzer",
+ family: "code-plugin",
+ name: "@openclaw/dronzer",
+ nextVersion: "1.0.1",
+ ownerHandle: "vintageayu",
+ sourceRepo: "VintageAyu/dronzer",
+ ignored: "drop-me",
+ });
+
+ expect(route.__path).toBe("/publish-plugin");
+ expect(() => route.__config.beforeLoad({ search })).toThrow();
+ expect(redirectMock).toHaveBeenCalledWith({
+ to: "/plugins/publish",
+ search,
+ });
+ });
+
+ it("redirects legacy skill publish links to /skills/publish", async () => {
+ const route = await loadRoute("../routes/publish-skill");
+ const search = route.__config.validateSearch({ updateSlug: "dronzer", ignored: "drop-me" });
+
+ expect(route.__path).toBe("/publish-skill");
+ expect(() => route.__config.beforeLoad({ search })).toThrow();
+ expect(redirectMock).toHaveBeenCalledWith({
+ to: "/skills/publish",
+ search,
+ });
+ });
+});
diff --git a/src/__tests__/upload.route.test.tsx b/src/__tests__/skills-publish-route.test.tsx
similarity index 99%
rename from src/__tests__/upload.route.test.tsx
rename to src/__tests__/skills-publish-route.test.tsx
index 526916dc..d2246b1d 100644
--- a/src/__tests__/upload.route.test.tsx
+++ b/src/__tests__/skills-publish-route.test.tsx
@@ -1,7 +1,7 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { strToU8, zipSync } from "fflate";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { Upload } from "../routes/publish-skill";
+import { Upload } from "../routes/skills/publish";
vi.mock("@tanstack/react-router", () => ({
createFileRoute: () => (config: { component: unknown }) => config,
diff --git a/src/components/Footer.test.tsx b/src/components/Footer.test.tsx
index ade83203..9d9ee6f6 100644
--- a/src/components/Footer.test.tsx
+++ b/src/components/Footer.test.tsx
@@ -58,12 +58,12 @@ describe("Footer", () => {
within(publish as HTMLElement)
.getByRole("link", { name: "Publish Skill" })
.getAttribute("href"),
- ).toBe("/publish-skill");
+ ).toBe("/skills/publish");
expect(
within(publish as HTMLElement)
.getByRole("link", { name: "Publish Plugin" })
.getAttribute("href"),
- ).toBe("/publish-plugin");
+ ).toBe("/plugins/publish");
expect(
within(community as HTMLElement)
.getByRole("link", { name: "GitHub" })
diff --git a/src/components/PackageSourceChooser.tsx b/src/components/PackageSourceChooser.tsx
index f6c4a170..6ef704dd 100644
--- a/src/components/PackageSourceChooser.tsx
+++ b/src/components/PackageSourceChooser.tsx
@@ -1,4 +1,4 @@
-import type { PackageCompatibility } from "clawhub-schema";
+import { DocsLinks, type PackageCompatibility } from "clawhub-schema";
import { Package } from "lucide-react";
import { useRef, useState } from "react";
import { formatPackageCompatibility } from "../lib/pluginPublishPrefill";
@@ -8,9 +8,6 @@ import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-const OPENCLAW_PLUGIN_PACKAGE_METADATA_DOCS_URL =
- "https://docs.openclaw.ai/plugins/sdk-setup#package-metadata";
-
export function PackageSourceChooser(props: {
files: File[];
totalBytes: number;
@@ -161,7 +158,7 @@ export function PackageSourceChooser(props: {
Missing required OpenClaw package metadata: {props.codePluginFieldIssues.join(", ")}. Add
these fields to package.json before publishing. See{" "}
-
+
New Version
diff --git a/src/lib/nav-items.ts b/src/lib/nav-items.ts
index 65e52787..4992f87d 100644
--- a/src/lib/nav-items.ts
+++ b/src/lib/nav-items.ts
@@ -163,13 +163,13 @@ export const FOOTER_NAV_SECTIONS: FooterNavSection[] = [
{
kind: "link",
label: "Publish Skill",
- to: "/publish-skill",
+ to: "/skills/publish",
search: { updateSlug: undefined },
},
{
kind: "link",
label: "Publish Plugin",
- to: "/publish-plugin",
+ to: "/plugins/publish",
search: {
ownerHandle: undefined,
name: undefined,
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 28566e14..2a29f457 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -29,6 +29,8 @@ import { Route as PluginsIndexRouteImport } from './routes/plugins/index'
import { Route as PackagesIndexRouteImport } from './routes/packages/index'
import { Route as UHandleRouteImport } from './routes/u/$handle'
import { Route as SoulsSlugRouteImport } from './routes/souls/$slug'
+import { Route as SkillsPublishRouteImport } from './routes/skills/publish'
+import { Route as PluginsPublishRouteImport } from './routes/plugins/publish'
import { Route as PluginsNewRouteImport } from './routes/plugins/new'
import { Route as PluginsNameRouteImport } from './routes/plugins/$name'
import { Route as PackagesNewRouteImport } from './routes/packages/new'
@@ -144,6 +146,16 @@ const SoulsSlugRoute = SoulsSlugRouteImport.update({
path: '/souls/$slug',
getParentRoute: () => rootRouteImport,
} as any)
+const SkillsPublishRoute = SkillsPublishRouteImport.update({
+ id: '/skills/publish',
+ path: '/skills/publish',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const PluginsPublishRoute = PluginsPublishRouteImport.update({
+ id: '/plugins/publish',
+ path: '/plugins/publish',
+ getParentRoute: () => rootRouteImport,
+} as any)
const PluginsNewRoute = PluginsNewRouteImport.update({
id: '/plugins/new',
path: '/plugins/new',
@@ -240,6 +252,8 @@ export interface FileRoutesByFullPath {
'/packages/new': typeof PackagesNewRoute
'/plugins/$name': typeof PluginsNameRouteWithChildren
'/plugins/new': typeof PluginsNewRoute
+ '/plugins/publish': typeof PluginsPublishRoute
+ '/skills/publish': typeof SkillsPublishRoute
'/souls/$slug': typeof SoulsSlugRoute
'/u/$handle': typeof UHandleRoute
'/packages/': typeof PackagesIndexRoute
@@ -276,6 +290,8 @@ export interface FileRoutesByTo {
'/packages/new': typeof PackagesNewRoute
'/plugins/$name': typeof PluginsNameRouteWithChildren
'/plugins/new': typeof PluginsNewRoute
+ '/plugins/publish': typeof PluginsPublishRoute
+ '/skills/publish': typeof SkillsPublishRoute
'/souls/$slug': typeof SoulsSlugRoute
'/u/$handle': typeof UHandleRoute
'/packages': typeof PackagesIndexRoute
@@ -313,6 +329,8 @@ export interface FileRoutesById {
'/packages/new': typeof PackagesNewRoute
'/plugins/$name': typeof PluginsNameRouteWithChildren
'/plugins/new': typeof PluginsNewRoute
+ '/plugins/publish': typeof PluginsPublishRoute
+ '/skills/publish': typeof SkillsPublishRoute
'/souls/$slug': typeof SoulsSlugRoute
'/u/$handle': typeof UHandleRoute
'/packages/': typeof PackagesIndexRoute
@@ -351,6 +369,8 @@ export interface FileRouteTypes {
| '/packages/new'
| '/plugins/$name'
| '/plugins/new'
+ | '/plugins/publish'
+ | '/skills/publish'
| '/souls/$slug'
| '/u/$handle'
| '/packages/'
@@ -387,6 +407,8 @@ export interface FileRouteTypes {
| '/packages/new'
| '/plugins/$name'
| '/plugins/new'
+ | '/plugins/publish'
+ | '/skills/publish'
| '/souls/$slug'
| '/u/$handle'
| '/packages'
@@ -423,6 +445,8 @@ export interface FileRouteTypes {
| '/packages/new'
| '/plugins/$name'
| '/plugins/new'
+ | '/plugins/publish'
+ | '/skills/publish'
| '/souls/$slug'
| '/u/$handle'
| '/packages/'
@@ -460,6 +484,8 @@ export interface RootRouteChildren {
PackagesNewRoute: typeof PackagesNewRoute
PluginsNameRoute: typeof PluginsNameRouteWithChildren
PluginsNewRoute: typeof PluginsNewRoute
+ PluginsPublishRoute: typeof PluginsPublishRoute
+ SkillsPublishRoute: typeof SkillsPublishRoute
SoulsSlugRoute: typeof SoulsSlugRoute
UHandleRoute: typeof UHandleRoute
PackagesIndexRoute: typeof PackagesIndexRoute
@@ -613,6 +639,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SoulsSlugRouteImport
parentRoute: typeof rootRouteImport
}
+ '/skills/publish': {
+ id: '/skills/publish'
+ path: '/skills/publish'
+ fullPath: '/skills/publish'
+ preLoaderRoute: typeof SkillsPublishRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/plugins/publish': {
+ id: '/plugins/publish'
+ path: '/plugins/publish'
+ fullPath: '/plugins/publish'
+ preLoaderRoute: typeof PluginsPublishRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/plugins/new': {
id: '/plugins/new'
path: '/plugins/new'
@@ -773,6 +813,8 @@ const rootRouteChildren: RootRouteChildren = {
PackagesNewRoute: PackagesNewRoute,
PluginsNameRoute: PluginsNameRouteWithChildren,
PluginsNewRoute: PluginsNewRoute,
+ PluginsPublishRoute: PluginsPublishRoute,
+ SkillsPublishRoute: SkillsPublishRoute,
SoulsSlugRoute: SoulsSlugRoute,
UHandleRoute: UHandleRoute,
PackagesIndexRoute: PackagesIndexRoute,
diff --git a/src/routes/about.tsx b/src/routes/about.tsx
index fbc0fe3c..de49697e 100644
--- a/src/routes/about.tsx
+++ b/src/routes/about.tsx
@@ -1,4 +1,5 @@
import { createFileRoute, Link } from "@tanstack/react-router";
+import { DocsLinks } from "clawhub-schema";
import type { LucideIcon } from "lucide-react";
import {
Banknote,
@@ -263,11 +264,7 @@ function AboutPage() {
Browse Skills
diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx
index b2aac26e..c8c76f13 100644
--- a/src/routes/dashboard.tsx
+++ b/src/routes/dashboard.tsx
@@ -204,7 +204,7 @@ export function Dashboard() {
@@ -245,7 +245,7 @@ export function Dashboard() {
Skills