diff --git a/convex/httpApiV1.handlers.test.ts b/convex/httpApiV1.handlers.test.ts index 98beeac2..1baaf052 100644 --- a/convex/httpApiV1.handlers.test.ts +++ b/convex/httpApiV1.handlers.test.ts @@ -2114,6 +2114,7 @@ describe("httpApiV1 handlers", () => { source: "skills-sh", slug: "find-skills", score: 5_095, + downloads: 3_062, canonicalUrl: "/skills-sh/vercel-labs/skills/find-skills", }, ]; diff --git a/convex/search.test.ts b/convex/search.test.ts index b7043591..e93a44df 100644 --- a/convex/search.test.ts +++ b/convex/search.test.ts @@ -1365,6 +1365,7 @@ describe("search helpers", () => { install: { reference: "skills-sh:acme/skills/calendar" }, icon: null, sourceIdentity: { lifetimeInstalls: 10_000_000 }, + downloads: 10_000_000, }); }); diff --git a/convex/search.ts b/convex/search.ts index 1d8bccbf..ba702aa8 100644 --- a/convex/search.ts +++ b/convex/search.ts @@ -621,7 +621,7 @@ type CanonicalSkillSearchResult = { // Compatibility fields retained for existing CLI/OpenClaw parsers. ownerHandle: string | null; version: string | null; - downloads: number | null; + downloads: number; updatedAt: number; }; @@ -812,7 +812,7 @@ function buildExternalCanonicalResult( native: null, ownerHandle: sourceOwner, version: null, - downloads: null, + downloads: digest.upstreamInstalls, }; } diff --git a/packages/clawhub/src/schema/schemas.test.ts b/packages/clawhub/src/schema/schemas.test.ts index 0b086ffa..e21f790c 100644 --- a/packages/clawhub/src/schema/schemas.test.ts +++ b/packages/clawhub/src/schema/schemas.test.ts @@ -74,6 +74,12 @@ describe("packages/clawhub skill metadata schema", () => { displayName: "Calendar", summary: "Calendar workflows", score: 6_110, + // The canonical downloads field normalizes the source-owned count: + // ClawHub downloads for native rows, skills.sh installs for mirrors. + version: null, + downloads: 99_000, + ownerHandle: "acme", + updatedAt: 10, canonicalUrl: "/skills-sh/acme/skills/calendar", official: false, featured: false, @@ -124,6 +130,7 @@ describe("packages/clawhub skill metadata schema", () => { ]); expect(parsed.results[0]?.install?.reference).toBe("skills-sh:acme/skills/calendar"); expect(parsed.results[0]?.trust?.sourceFreshness).toBe("observed-only"); + expect(parsed.results[0]?.downloads).toBe(99_000); }); it("parses pending package publish responses with legacy IDs", () => { diff --git a/packages/schema/src/schemas.test.ts b/packages/schema/src/schemas.test.ts index 1c0543e9..9626f412 100644 --- a/packages/schema/src/schemas.test.ts +++ b/packages/schema/src/schemas.test.ts @@ -508,6 +508,22 @@ describe("clawhub-schema", () => { expect(parsed.results[0]?.owner?.displayName).toBe("OpenClaw"); }); + it("parses canonical skills.sh download counters", () => { + // Search normalizes skills.sh installs into the canonical downloads field, + // so already-released clients can parse mixed-source results unchanged. + const parsed = parseArk( + ApiV1SearchResponseSchema, + { + results: [ + { slug: "humanizer", score: 6_110, version: null, downloads: 2_190, ownerHandle: "acme" }, + ], + }, + "Search", + ); + + expect(parsed.results[0]?.downloads).toBe(2_190); + }); + it("parses flattened skill verification envelopes", () => { const parsed = parseArk( ApiV1SkillVerifyResponseSchema, diff --git a/src/routes/skills/-types.ts b/src/routes/skills/-types.ts index 3a94834e..1ecef0b9 100644 --- a/src/routes/skills/-types.ts +++ b/src/routes/skills/-types.ts @@ -77,7 +77,7 @@ export type SkillSearchEntry = { } | null; ownerHandle: string | null; version: string | null; - downloads: number | null; + downloads: number; updatedAt: number; };